Repository navigation
fix(cli): never honor memory agent budgets from workspace scope - #13508
Conversation
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
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
left a comment
There was a problem hiding this comment.
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)
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
left a comment
There was a problem hiding this comment.
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
0is a strictly better probe thantruewas. The list-driven test builds a Workspace payload fromWORKSPACE_RESTRICTED_SETTINGSitself, then asserts for every key thatmerged?.[key]isundefinedand that a warning namingsection.keyexists.stripSettingKeysskips only onsource?.[key] === undefined, so feeding0exercises the falsy-numeric path a truthiness-based strip would silently pass through — and0is the exact value the restriction exists to block, since it disables the limit entirely. A leaked0failstoBeUndefined(), so the assertion is not vacuous. TheallowedInsecureVoiceBaseUrlsarray special-case is preserved, and every other key still getstrue.- The membership assertion is redundant by design, not by accident.
exposes every key in dotted form for the dialog filteralready assertsWORKSPACE_RESTRICTED_SETTING_KEYSequals the list mapped to dotted form, so atoContainfor 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
left a comment
There was a problem hiding this comment.
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.
What this PR does
The workspace restriction itself is already on
main: #13462 addedmemory.agentMaxTurnsandmemory.agentTimeoutMinutestoWORKSPACE_RESTRICTED_SETTINGS, together with theit.each([undefined, 0, 25])pin inpackages/cli/src/config/settings.test.ts. What merges here sits on top of that:User/System/SystemDefaults scopes only; Workspace values are ignored with a warning.— replacingConfigure in user or system settings., which named two of the three honored scopes and never said the value is dropped.agentTimeoutMinutesdescription 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, anddocs/users/configuration/settings.md:434carries the same five-agent enumeration, so the schema and the docs row agree.agentMaxTurnskeeps the four-agent list on both surfaces: the migrator hardcodesmaxTurns: 1.packages/vscode-ide-companion/schemas/settings.schema.jsonis regenerated from those strings viascripts/generate-settings-schema.ts(never hand-edited).0— the value the restriction exists to block — for the two numeric keys instead of a boolean, and one membership assertion pins both keys inWORKSPACE_RESTRICTED_SETTING_KEYSbeside 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
0disables 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 onmainby #13462, which is why this diff contains nosettingsUtils.tschange.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 twotype: 'number'settings, so it could not exercise the0that 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.tsand confirmgit diff --exit-codeonpackages/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 twomemory.agent*descriptions in the generated JSON againstdocs/users/configuration/settings.md:434-435. To check the test payload:npx vitest run src/config/settings.test.tsinpackages/cliand confirmstrips and warns for every listed key, driven by the list itselfand the threeignores workspace memory budget zero while preserving user budget …arms pass.The enforcement behavior itself (workspace
0stripped with a per-key warning, user-scope0honored) is pinned onmainbyit.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.tsinpackages/cli→ 237 passed, 2 skipped.npx prettier --checkclean on all four touched files. Regenerating the schema produces a byte-identical file.npx tsc --noEmitinpackages/clireports the same 218 pre-existing error lines before and after this change on this machine (unbuilt siblingdist/in the local worktree); none of them are insrc/config/settings*.Tested on
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; focusedpackages/clivitest run, prettier, tsc and the schema generator. No scratch HOME/workspace needed because no runtime behavior changes here.Risk & Scope
mainwith fix(core): honor memory.agentMaxTurns in the user-scoped memory dream #13462, not here.src/config/settings.test.tsrun instead). Whether any other numeric settings deserve restriction is a separate policy question, unchanged here.Linked Issues
Closes #13477
Refs #13462
Closesis bookkeeping: the enforcement that issue asked for is onmainvia #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重新生成,不手改。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。风险与范围
main,不在这里。src/config/settings.test.ts)。是否还有其它数值型设置需要受限是独立的策略问题,本 PR 不涉及。关联 issue:Closes #13477、Refs #13462。
Closes只是账务——#13477 要求的修复已由 #13462 落在main,合并本 PR 是关闭该 issue,而不是交付那层强制。