Skip to content

fix(cli): never honor memory agent budgets from workspace scope - #13508

Merged
yiliang114 merged 5 commits into
mainfrom
fix/memory-agent-caps-workspace-restriction
Oct 6, 2026
Merged

yiliang114 merged 5 commits into
mainfrom
fix/memory-agent-caps-workspace-restriction

Conversation

@yiliang114

@yiliang114 yiliang114 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

The workspace restriction itself is already on main: #13462 added memory.agentMaxTurns and memory.agentTimeoutMinutes to WORKSPACE_RESTRICTED_SETTINGS, together with the it.each([undefined, 0, 25]) pin in packages/cli/src/config/settings.test.ts. What merges here sits on top of that:

  • Both settings' descriptions now state the scope rule in the words the docs table and the runtime warning already use — User/System/SystemDefaults scopes only; Workspace values are ignored with a warning. — replacing Configure in user or system settings., which named two of the three honored scopes and never said the value is dropped.
  • The agentTimeoutMinutes description also names the fifth consumer of that budget, the memory metadata migration (packages/core/src/memory/metadata-migration.ts), which the four-agent enumeration left out, and docs/users/configuration/settings.md:434 carries the same five-agent enumeration, so the schema and the docs row agree. agentMaxTurns keeps the four-agent list on both surfaces: the migrator hardcodes maxTurns: 1.
  • packages/vscode-ide-companion/schemas/settings.schema.json is regenerated from those strings via scripts/generate-settings-schema.ts (never hand-edited).
  • The list-driven strip test now feeds 0 — the value the restriction exists to block — for the two numeric keys instead of a boolean, and one membership assertion pins both keys in WORKSPACE_RESTRICTED_SETTING_KEYS beside the existing list-driven test rather than in a second describe.

Why it's needed

These two keys budget the background memory agents' turn count and wall-clock time, and 0 disables each limit entirely. Those agents are auto-started, run auto-approved, and hold write access to the cross-project memory directory every session reads from, so #13477 was right that a cloned repository must not be able to set those budgets at workspace scope. That hole is closed on main by #13462, which is why this diff contains no settingsUtils.ts change.

What was left is the description surface: the string VS Code shows while completing **/.qwen/settings.json — the very file whose value gets stripped — said only where the key is preferred, and an operator managing a fleet through SystemDefaults read "user or system settings" as excluding a scope that is in fact honored. Separately, the list-driven test fed a boolean to two type: 'number' settings, so it could not exercise the 0 that the restriction exists for.

Reviewer Test Plan

How to verify

Wording only, plus one test payload — there is no behavior change left in this diff to exercise by hand. To check the strings: run npx tsx scripts/generate-settings-schema.ts and confirm git diff --exit-code on packages/vscode-ide-companion/schemas/settings.schema.json (the CI staleness gate runs the same comparison via .github/scripts/check-settings-schema.sh), then read the two memory.agent* descriptions in the generated JSON against docs/users/configuration/settings.md:434-435. To check the test payload: npx vitest run src/config/settings.test.ts in packages/cli and confirm strips and warns for every listed key, driven by the list itself and the three ignores workspace memory budget zero while preserving user budget … arms pass.

The enforcement behavior itself (workspace 0 stripped with a per-key warning, user-scope 0 honored) is pinned on main by it.each([undefined, 0, 25]) and was verified in #13462.

Evidence (Before & After)

N/A (non-UI, no behavior change). Local run on this branch: npx vitest run src/config/settings.test.ts in packages/cli → 237 passed, 2 skipped. npx prettier --check clean on all four touched files. Regenerating the schema produces a byte-identical file. npx tsc --noEmit in packages/cli reports the same 218 pre-existing error lines before and after this change on this machine (unbuilt sibling dist/ in the local worktree); none of them are in src/config/settings*.

Tested on

OS Status
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

Windows/Linux left to CI; the change is settings-description wording and test payload only.

Environment (optional)

Branch worktree with the repository's existing node_modules; focused packages/cli vitest run, prettier, tsc and the schema generator. No scratch HOME/workspace needed because no runtime behavior changes here.

Risk & Scope

  • Main risk or tradeoff: none at runtime — the diff is two description strings, their generated mirror, the matching docs row, and test payload. A repository that set these budgets at workspace scope loses them, but that behavior shipped on main with fix(core): honor memory.agentMaxTurns in the user-scoped memory dream #13462, not here.
  • Not validated / out of scope: full-repo test suite (focused src/config/settings.test.ts run instead). Whether any other numeric settings deserve restriction is a separate policy question, unchanged here.
  • Breaking changes / migration notes: none in this diff.

Linked Issues

Closes #13477
Refs #13462

Closes is bookkeeping: the enforcement that issue asked for is on main via #13462, so merging this PR closes the issue rather than delivering it.

中文说明

本次改动

限制本身已经在 main 上:#13462 把 memory.agentMaxTurns 和 memory.agentTimeoutMinutes 加进了 WORKSPACE_RESTRICTED_SETTINGS,并带上了 packages/cli/src/config/settings.test.ts 里 it.each([undefined, 0, 25]) 这个固定点。本 PR 合入的是它之上的部分:

  • 两个设置的描述改用文档表格与运行时告警已有的措辞——User/System/SystemDefaults scopes only; Workspace values are ignored with a warning.——替换掉原来的 Configure in user or system settings.(只列了三个被尊重作用域中的两个,也没说这个值会被丢弃)。
  • agentTimeoutMinutes 的描述补上该预算的第五个消费方,即记忆元数据迁移(packages/core/src/memory/metadata-migration.ts),docs/users/configuration/settings.md:434 也同步为同样的五个 agent 列举,因此 schema 与文档行一致;agentMaxTurns 在两个面上都保持四个 agent,因为迁移器硬编码 maxTurns: 1。
  • packages/vscode-ide-companion/schemas/settings.schema.json 由 scripts/generate-settings-schema.ts 重新生成,不手改。
  • 列表驱动的剥离测试改为对两个数值 key 喂 0(正是该限制要防的值)而不是布尔值;成员断言合并到既有列表驱动测试旁边,不再另开一个 describe。

动机

这两个 key 是后台 memory agent 的轮次与时长预算,0 表示完全取消限制。这些 agent 自动启动、自动批准、且持有每个会话都会读取的跨项目记忆目录的写权限,所以 #13477 指出「被克隆的仓库不应能在 workspace 作用域设置这两个预算」是对的。该漏洞已由 #13462 在 main 上关闭,这也是本 diff 里没有 settingsUtils.ts 改动的原因。

剩下的是描述面:VS Code 在补全 **/.qwen/settings.json(正是取值会被剥离的那个文件)时展示的那句话,只说了该 key 更适合放在哪里;而通过 SystemDefaults 管理机群的运维会把「user or system settings」读成一个其实被尊重的作用域不受支持。另外,列表驱动的测试过去给两个 type: 'number' 的设置喂布尔值,因此跑不到限制所要防的那个 0。

验证方式

只有措辞与一处测试载荷,diff 里没有可手工验证的行为变化。核对字符串:执行 npx tsx scripts/generate-settings-schema.ts,确认 packages/vscode-ide-companion/schemas/settings.schema.json 上 git diff --exit-code 为空(CI 的新鲜度门通过 .github/scripts/check-settings-schema.sh 做同样比较),再把生成 JSON 里两个 memory.agent* 描述与 docs/users/configuration/settings.md:434-435 对照。核对测试载荷:在 packages/cli 执行 npx vitest run src/config/settings.test.ts,确认 strips and warns for every listed key, driven by the list itself 与三条 ignores workspace memory budget zero while preserving user budget … 全绿。

强制行为本身(workspace 的 0 被剥离并按 key 告警、user 作用域的 0 被保留)已由 main 上的 it.each([undefined, 0, 25]) 固定,并在 #13462 中验证过。

本分支本地结果:packages/cli 下 npx vitest run src/config/settings.test.ts → 237 passed / 2 skipped;四个改动文件 npx prettier --check 全通过;重新生成 schema 得到逐字节相同的文件;packages/cli 下 npx tsc --noEmit 在本机改动前后都是同样的 218 行既有报错(本地 worktree 中兄弟包 dist/ 未构建),其中没有一条位于 src/config/settings*。Windows/Linux 交给 CI。

风险与范围

  • 主要风险:运行时没有——本 diff 是两条描述字符串、它们的生成镜像、对应的文档行和测试载荷。在 workspace 作用域设置过这两个预算的仓库会失去该设置,但那个行为随 fix(core): honor memory.agentMaxTurns in the user-scoped memory dream #13462 落在 main,不在这里。
  • 未验证 / 范围外:未跑全仓测试(改为跑 src/config/settings.test.ts)。是否还有其它数值型设置需要受限是独立的策略问题,本 PR 不涉及。
  • 破坏性变更:本 diff 无。

关联 issue:Closes #13477、Refs #13462。Closes 只是账务——#13477 要求的修复已由 #13462 落在 main,合并本 PR 是关闭该 issue,而不是交付那层强制。

memory.agentMaxTurns and memory.agentTimeoutMinutes cap the turn count
and wall-clock time of the auto-approved background memory agents that
hold write access to the cross-project memory directory, and 0 disables
each limit. A cloned repository could set them in .qwen/settings.json
and remove those budgets. Add both keys to WORKSPACE_RESTRICTED_SETTINGS
so workspace values are stripped with a warning like the other
restricted keys, and state the allowed scopes in the setting
descriptions.

Closes #13477
Refs #13462
yiliang114 and others added 2 commits October 6, 2026 17:00
Resolve the docs/users/configuration/settings.md conflict by keeping the
wording that landed on main via #13462 ("User/System/SystemDefaults scopes
only; Workspace values are ignored with a warning."), which states the same
workspace restriction this PR enforces more precisely than the branch's
"Configure in user or system settings." phrasing.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmuwgqbuc9u

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: reverse audit — stopped before round 3 by the review time budget.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

— qwen3.8-max via Qwen Code /review (v0.25.0)

Comment thread packages/cli/src/config/settings.test.ts Outdated
Comment thread packages/cli/src/config/settingsSchema.ts Outdated
Comment thread packages/cli/src/config/settings.test.ts
Comment thread packages/cli/src/config/settingsSchema.ts Outdated
yiliang114 and others added 2 commits October 6, 2026 20:52
R1-1: main already carries the workspace restriction (#13462) plus
it.each([undefined, 0, 25]), which covers the strip, the per-key warning
and user-scope 0 survival, so drop the two restating cases and move the
list membership assertion into the existing WORKSPACE_RESTRICTED_SETTINGS
describe instead of keeping a second describe for the same rule. The
list-driven strip test keeps feeding 0 for the two numeric budgets.

R1-2: the appended scope sentence named two of the three honored scopes
and never stated the consequence; use the docs row wording verbatim.

R1-4: name the fifth getMemoryAgentTimeoutMinutes() consumer
(memory/metadata-migration.ts) in the timeout description. agentMaxTurns
is unaffected because the migrator hardcodes maxTurns: 1.

Companion JSON regenerated via scripts/generate-settings-schema.ts.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmuwnio38a6
…tMinutes row

R1-4 follow-up. On main both surfaces carried the same four-agent
enumeration, so the schema-only edit in the previous commit introduced a
docs/schema disagreement instead of closing a pre-existing one. Mirror
the fifth getMemoryAgentTimeoutMinutes() consumer into the docs row.
memory.agentMaxTurns stays at four agents on both surfaces because
metadata-migration.ts hardcodes maxTurns: 1.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmuwnio38a6

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical-only scan at head 9cd07ab29ea0148ace1ef6ee1e2389da57f1d1a4 — complete. Verdict: APPROVE.

No Critical found, and this PR has no historical blocking issue to clear: the only review round on it filed four findings, all severity S, and all four inline threads are resolved with zero unresolved. Under this channel's Critical-only policy those Suggestions are read but not tracked, and nothing in them bears on merge.

The diff is description strings, the docs row that mirrors them, the regenerated VS Code schema, and two test changes. That surface has one way to be wrong — describing a protection that does not exist — so that is what I checked first.

The scope sentence describes a restriction that is live at this head

Both descriptions now end with User/System/SystemDefaults scopes only; Workspace values are ignored with a warning. The body states the enforcement itself landed on main via #13462 and that this diff therefore carries no settingsUtils.ts change. Verified rather than taken: packages/cli/src/config/settingsUtils.ts at this head holds { section: 'memory', key: 'agentMaxTurns' } and { section: 'memory', key: 'agentTimeoutMinutes' } inside WORKSPACE_RESTRICTED_SETTINGS (:280-281). That list drives both the pre-merge Workspace strip and the "ignored" warning from a single source, so the string an operator reads in VS Code while completing **/.qwen/settings.json now agrees with what the runtime actually does to that file's value. The replaced wording — "Configure in user or system settings." — both omitted a scope that is honored and never said the value is dropped, so this is a correction rather than a restatement.

The five-consumer enumeration is accurate, and the asymmetry between the two keys is deliberate

agentTimeoutMinutes gains a fifth governed consumer, the memory metadata migration, while agentMaxTurns keeps four. I confirmed the split is factual rather than an oversight: packages/core/src/memory/metadata-migration.ts reads config.getMemoryAgentTimeoutMinutes() ?? 10 at :506 and hardcodes maxTurns: 1 at :505. So the migrator really is governed by the timeout budget and really is not governed by the turn budget, and listing it under only one of the two keys is the correct outcome. docs/users/configuration/settings.md:434 carries the same five-agent enumeration, so the docs row and the schema string agree.

The generated packages/vscode-ide-companion/schemas/settings.schema.json mirrors both settingsSchema.ts descriptions byte-for-byte, which is what the generator contract requires; Test (ubuntu-latest, Node 22.x) is green at this head, so no drift check tripped.

Both test changes hold up

  • 0 is a strictly better probe than true was. The list-driven test builds a Workspace payload from WORKSPACE_RESTRICTED_SETTINGS itself, then asserts for every key that merged?.[key] is undefined and that a warning naming section.key exists. stripSettingKeys skips only on source?.[key] === undefined, so feeding 0 exercises the falsy-numeric path a truthiness-based strip would silently pass through — and 0 is the exact value the restriction exists to block, since it disables the limit entirely. A leaked 0 fails toBeUndefined(), so the assertion is not vacuous. The allowedInsecureVoiceBaseUrls array special-case is preserved, and every other key still gets true.
  • The membership assertion is redundant by design, not by accident. exposes every key in dotted form for the dialog filter already asserts WORKSPACE_RESTRICTED_SETTING_KEYS equals the list mapped to dotted form, so a toContain for the two memory keys adds no coverage the equality does not. Placing it beside the existing list-driven test rather than in a second describe keeps the three consumers of the list — strip, warning, dialog filter — pinned in one place, which is the point of the R4-3 comment above it. Harmless and consistent with the file's existing shape.

CI

Healthy at this head — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), Native Windows settings and streams, Native Linux CLI boundary, the OpenTUI no-flicker and TUI parity gates and both Desktop Shell legs pass. web-shell E2E Smoke was still pending at review time; nothing failed and no failure is attributable to this PR.

Changed files sit under packages/cli/src/config/, so require_code_owner_review applies. This approval is submitted as qqqys.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier: Scan. Four-file change: two description strings in settingsSchema.ts, their generated mirror in settings.schema.json, the matching docs table row, and a test payload fix. No behavior change — workspace enforcement of both keys ships on main via #13462.

Scope. settingsSchema.ts and settings.schema.json (description strings only), docs/users/configuration/settings.md (matching docs row), settings.test.ts (list-driven payload and membership assertion). settingsUtils.ts is confirmed absent from the net diff against base — the restriction entries are already on main.


Findings

No blocking findings.

Consistency checks

Schema ↔ generated JSON — settings.schema.json is a byte-identical regeneration of the two changed description strings in settingsSchema.ts. Both surfaces carry the full sentence User/System/SystemDefaults scopes only; Workspace values are ignored with a warning. ✓

Schema ↔ docs row — agentTimeoutMinutes: five-agent list (extraction, dream, remember, skill review, memory metadata migration) matches across schema and docs row; scope warning matches. agentMaxTurns: four-agent list (metadata-migration hardcodes maxTurns: 1, correctly excluded) matches; scope warning matches. Pre-existing asymmetry: the schema carries "Useful for slow local models…" but the docs row does not; this is not introduced by this PR. ✓

Test payload — the list-driven strips and warns for every listed key test now feeds 0 for agentMaxTurns and agentTimeoutMinutes rather than true. Both settings are declared type: number, minimum: 0; 0 is the exact value the restriction exists to block and true would silently coerce. ✓

Membership assertion placement — the lists both memory budgets as workspace-restricted check lands inside the existing WORKSPACE_RESTRICTED_SETTINGS describe block rather than in a separate memory agent budget scope handling block (which the second commit removed). The two behavioral cases dropped from that block (honors user-scope 0, strips workspace 0 and warns per key) are already covered by main's it.each([undefined, 0, 25]) pin. ✓

Cross-check — qqqys approved at this exact head after a Critical-only scan; four prior S-level threads from qwen-code-review-bot are all resolved. No misses found — the PR is description-and-test-only with no mechanism to audit beyond consistency.


No blocking findings. Approval blockers: none.

Reviewed with AI assistance.

@yiliang114
yiliang114 added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit 86b9f13 Oct 6, 2026
103 of 104 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category/security Security and privacy review/self-reported The linked issue was opened by the PR author (self-reported) scope/memory Memory and context management scope/settings Settings and preferences type/bug Something isn't working as expected

Projects

None yet

4 participants