refactor(serve): default project memory to workspace scope - #8856
Conversation
|
Re-running the gate on Template still looks good ✓ — all sections filled in, bilingual summary and migration note intact. Problem: unchanged — real and concrete, not theoretical. The daemon routes every other resource per registered workspace, so project memory defaulting to the shared Git-root scope was an observable inconsistency; #8854 documents it with acceptance criteria, and this completes the opt-in isolation landed in #8056. Still self-reported by the same author ( Direction: aligned. Workspace isolation as the safe daemon default, with the legacy Size: unchanged from the previous pass — one core path touched, Approach: unchanged and still right — one injection point (option → non-blank launch env → Risk: no elevated risk signals — no files match the revert-correlated high-risk paths. Moving on to code review. 🔍 中文说明在 模板依然完整 ✓ —— 各节齐全,中文摘要与迁移说明俱在。 问题:不变 —— 真实具体,非理论性问题。daemon 的其他资源均按注册 workspace 路由,项目记忆默认共享 Git-root scope 是可观测的不一致;#8854 已带验收标准记录,本 PR 收尾 #8056 落地的 opt-in 隔离。仍为同一作者自报( 方向:对齐。workspace 隔离作为 daemon 的安全默认值,legacy 规模:与上轮一致 —— 触及一条核心路径 方案:不变且依然合理 —— 单一注入点(选项 → 非空白启动环境 → 风险:无升级风险信号 —— 无文件命中与 revert 相关的高风险路径。 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-reviewing at the new head
No critical issues, no project-convention violations. The one scope question from the previous round — the exclusion also closes the standalone CLI's project- CI evidenceUnattended re-run — I never build or run PR code; the evidence below is the PR's own CI on the reviewed commit, fetched through the API. Everything is green on
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 No workflow runs with the On sandboxed evidence: the ✅ 中文说明代码审查:在新 head CI 证据:无人值守 re-run,不构建/运行 PR 代码;以上为所审提交上 PR 自身 CI 经 API 获取的结果 —— 沙箱证据:此前的 ✅ — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 3/5 — every stage is green and the diff is now verified three independent ways; the cap is pure policy — fork Stepping back: this re-run added one commit — a merge of Two things a human should own, neither a defect:
Verdict: defer. ⏸️ @wenshao approved this exact commit minutes before this re-run — that is one of the two approvals 中文说明冷静回看:本次 re-run 只新增了一个提交 —— 合并 两项交由人工决定,均非缺陷:其一,策略决定已基本落定但值得最后点头 —— 排除项关闭 standalone 项目 结论:暂缓。⏸️ @wenshao 在本次 re-run 前几分钟已批准该提交 —— 那是 — Qwen Code · qwen3.8-max Reviewed at |
|
@qwen-code /verify |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 31 passed · 0 failed · 31 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:31 通过 · 0 失败 · 31 总计 Verification reportPR 8856 deep verificationVerdict: 中文摘要
Central claim and A/BCentral claim: a The oracle is the per-runtime
Load-bearing proof of the default ( Mutation matrix (no survivors)Each mutant edits exactly one guard of the new
Every mutant is killed by its intended test with an expected-vs-actual assertion (behavioural, not import/compile failure); each mutant kills only what its guard pins (M4 also trips case 1 because writing the default into Reviewer Test Plan walk-through
Findings (non-blocking)
Not covered
MethodologyEnvironment: CI verify container ( Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 4 scenario(s). — Qwen Code · serve A/B |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Not explored to full depth (tool budget reached): This PR makes workspace-scoped project memory the default...: did not run npm run typecheck or the changed vitest file to confirm compilation/green tests (review-only evidence from code reading).; This PR makes workspace-scoped project memory the default...: did not fully trace the workspace .env / settings.env overlay allowlist ( shared-env-keys.ts ) — the merge mechanism is pre-existing and unchanged by this dif…; This PR makes workspace-scoped project memory the default...: one repo-wide grep returned 31 truncated trailing lines; the visible portion plus follow-up targeted greps covered cli, core, desktop, and integration-tests, bu…; This PR makes workspace-scoped project memory the default...: did not trace whether any pre-handler CLI bootstrap ( loadEnvironment mutating process.env from the primary workspace's .env ) runs before runQwenServeImpl…; You are review agent reverse-audit — Reverse audit agen...: couldn't verify whether the session-shell subprocess env includes the injected scope (code comment suggests the daemon's process.env) — a potential new default-…, and 3 more.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 未探索到全部深度(达到工具调用预算):This PR makes workspace-scoped project memory the default...:did not run npm run typecheck or the changed vitest file to confirm compilation/green tests (review-only evidence from code reading).;This PR makes workspace-scoped project memory the default...:did not fully trace the workspace .env / settings.env overlay allowlist ( shared-env-keys.ts ) — the merge mechanism is pre-existing and unchanged by this dif…;This PR makes workspace-scoped project memory the default...:one repo-wide grep returned 31 truncated trailing lines; the visible portion plus follow-up targeted greps covered cli, core, desktop, and integration-tests, bu…;This PR makes workspace-scoped project memory the default...:did not trace whether any pre-handler CLI bootstrap ( loadEnvironment mutating process.env from the primary workspace's .env ) runs before runQwenServeImpl…;You are review agent reverse-audit — Reverse audit agen...:couldn't verify whether the session-shell subprocess env includes the injected scope (code comment suggests the daemon's process.env) — a potential new default-…,另有 3 条。
— qwen3.8-max via Qwen Code /review (v0.21.8)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Not explored to full depth (tool budget reached): This PR makes workspace-scoped project memory the default...: did not execute run-qwen-serve.test.ts / serve.test.ts (verification relied on code inspection; CI covers execution)..
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 未探索到全部深度(达到工具调用预算):This PR makes workspace-scoped project memory the default...:did not execute run-qwen-serve.test.ts / serve.test.ts (verification relied on code inspection; CI covers execution).。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| expect(logContent).toContain('project memory scope resolved'); | ||
| expect(logContent).toContain('projectMemoryScope=workspace'); | ||
| expect(logContent).toContain('projectMemoryScopeSource=default'); |
There was a problem hiding this comment.
[Suggestion] The new scope-log assertions depend on QWEN_CODE_MEMORY_PROJECT_SCOPE being absent from the ambient environment, but this test never saves/deletes/restores it — unlike the it.each scope tests this same diff rewrites (which snapshot the variable around line 3783). Probe-confirmed: with the variable exported, this test fails spuriously at projectMemoryScopeSource=default even though the code under test is correct; with a snapshot/delete/restore guard the contaminated arm passes. — Failure scenario: a developer or CI runner with QWEN_CODE_MEMORY_PROJECT_SCOPE exported (plausible for anyone working on this very feature) boots the daemon with source=environment, producing a red test about logging whose cause is invisible from the test name.
Fix (mirrors the originalScope pattern from the it.each block — applied before the runQwenServe call and in the existing finally):
const origScope = process.env['QWEN_CODE_MEMORY_PROJECT_SCOPE'];
delete process.env['QWEN_CODE_MEMORY_PROJECT_SCOPE'];
// ... in the existing finally block:
if (origScope === undefined) {
delete process.env['QWEN_CODE_MEMORY_PROJECT_SCOPE'];
} else {
process.env['QWEN_CODE_MEMORY_PROJECT_SCOPE'] = origScope;
}中文说明
[建议] 新增的 scope 日志断言依赖运行环境中不存在 QWEN_CODE_MEMORY_PROJECT_SCOPE,但该测试从未保存/删除/恢复这个变量——同一 diff 重写的 it.each scope 测试(约第 3783 行处)却做了快照保护。已通过探测复现:导出该变量时,此测试会在 projectMemoryScopeSource=default 处误报失败,尽管被测代码是正确的;加上快照/删除/恢复保护后,被污染的场景也能通过。— 失败场景:开发者或 CI runner 导出了 QWEN_CODE_MEMORY_PROJECT_SCOPE(对正在开发这个功能的人来说很常见),daemon 以 source=environment 启动,产生一个与日志本身无关的红色测试,且从测试名称完全看不出原因。
— qwen3.8-max via Qwen Code /review (v0.21.8)
There was a problem hiding this comment.
已修复 + 验证证据:65139068f8;在外部导出 QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root 时,定向日志测试 1/1 通过;完整 run-qwen-serve.test.ts 246/246 通过。
| daemonLog.info('project memory scope resolved', { | ||
| projectMemoryScope: resolvedMemoryProjectScope, | ||
| projectMemoryScopeSource: memoryProjectScopeSource, | ||
| }); |
There was a problem hiding this comment.
[Suggestion] The boot log records only the interpreted scope and source, never the raw configured value, and reports projectMemoryScopeSource=environment even when the environment value was unrecognized and merely fell through to git-root. Probe-confirmed: QWEN_CODE_MEMORY_PROJECT_SCOPE=workspce (typo) and a deliberate git-root produce byte-identical daemon log lines. — Failure scenario: a maintainer debugging "why do two daemon workspaces share project memory?" reads projectMemoryScope=git-root projectMemoryScopeSource=environment, which reads as "the environment explicitly chose git-root", sending them to argue about a setting nobody ever made, while the raw typo'd value appears nowhere in the log.
| daemonLog.info('project memory scope resolved', { | |
| projectMemoryScope: resolvedMemoryProjectScope, | |
| projectMemoryScopeSource: memoryProjectScopeSource, | |
| }); | |
| daemonLog.info('project memory scope resolved', { | |
| projectMemoryScope: resolvedMemoryProjectScope, | |
| projectMemoryScopeSource: memoryProjectScopeSource, | |
| projectMemoryScopeRaw: memoryProjectScopeValue, | |
| }); |
中文说明
[建议] 启动日志只记录解析后的 scope 和来源,从不记录原始配置值;即使环境变量的值无法识别、只是回落到 git-root,日志仍显示 projectMemoryScopeSource=environment。已通过探测复现:QWEN_CODE_MEMORY_PROJECT_SCOPE=workspce(拼写错误)和显式设置的 git-root 会产生逐字节相同的 daemon 日志行。— 失败场景:维护者排查"为什么两个 daemon workspace 共享项目记忆?"时读到 projectMemoryScope=git-root projectMemoryScopeSource=environment,会理解为"环境变量明确选择了 git-root",从而去争论一个根本没人做过的设置,而真正拼错的原始值在日志中无处可见。
— qwen3.8-max via Qwen Code /review (v0.21.8)
There was a problem hiding this comment.
已修复 + 验证证据:65139068f8;启动日志现记录 projectMemoryScopeRaw,定向日志测试 1/1、完整 run-qwen-serve.test.ts 246/246 通过。
|
Real daemon E2E: PASS Commands: npm run build && npm run bundle
npx tsx .qwen/e2e-tests/issue-8854-daemon-memory-workspace.tsThe harness started the built Evidence:
The E2E artifacts remain under the gitignored |
|
Final-head verification update: reran after fast-forwarding to
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and the unit suite was not run on Windows locally (the platform relevant to the Windows env-case finding in Needs Human Review).
Not explored to full depth (tool budget reached): This PR makes exact-workspace project-memory isolation th...: none — all checks above completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; This PR makes exact-workspace project-memory isolation th...: none — all checks above completed within budget..
中文说明
已审查。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and the unit suite was not run on Windows locally (the platform relevant to the Windows env-case finding in Needs Human Review)。
未探索到全部深度(达到工具调用预算):This PR makes exact-workspace project-memory isolation th...:none — all checks above completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;This PR makes exact-workspace project-memory isolation th...:none — all checks above completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| | `QWEN_SERVE_RATE_LIMIT_WINDOW_MS` | Env fallback for `--rate-limit-window-ms`. | | ||
| | `QWEN_CODE_MEMORY_PROJECT_SCOPE` | `workspace` keys project memory by the exact workspace dir; `git-root` selects the legacy shared scope. When unset, the daemon injects `workspace`; unrecognized values warn once and retain the legacy `git-root` behavior. Propagates via the runtime base env, not `childEnvOverrides`; `--memory-project-scope` wins. Each workspace remember/forget/dream lane caps pending tasks at `MAX_PENDING = 16`; N workspaces allow up to 16·N queued tasks with no daemon-wide cap. | | ||
|
|
||
| Blank `QWEN_CODE_MEMORY_PROJECT_SCOPE` values are treated as unset and therefore default to `workspace`; unrecognized non-empty values still warn once and retain the legacy `git-root` behavior. |
There was a problem hiding this comment.
[Suggestion] The documented guarantee that a workspace .env/settings.env cannot override the frozen memory-project scope does not hold on the serve fast path (runtime-probe verified): loadServeFastPathEnvironment writes every non-excluded key from the primary workspace's .env/settings.env into process.env when the launch env lacks it, and QWEN_CODE_MEMORY_PROJECT_SCOPE is in none of the exclusion lists (PROJECT_ENV_HARDCODED_EXCLUSIONS, isLoaderEnvKey, DEFAULT_EXCLUDED_ENV_VARS) — Failure scenario: plain qwen serve with no flag and no launch env in a repo whose .env sets QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root → the key lands in process.env before runQwenServeImpl snapshots it, the daemon freezes git-root into every runtime (the exact sharing this PR removes), and the boot log records projectMemoryScopeSource=environment, pointing operators at the launch env when the value came from a workspace file. The adjacent DEV key is already hardcoded-excluded with a dedicated test in run-qwen-serve.test.ts.
Suggested fix: add QWEN_CODE_MEMORY_PROJECT_SCOPE to PROJECT_ENV_HARDCODED_EXCLUSIONS in packages/cli/src/config/shared-env-keys.ts so every project-env loader rejects it from workspace env files; alternatively correct the docs to state that a workspace env file at the serve startup directory can still seed the scope when flag and launch env are both absent.
中文说明
文档中"workspace 的 .env/settings.env 无法覆盖冻结的 memory-project scope"这一保证在 serve 快速路径上不成立(已通过运行时探针验证):loadServeFastPathEnvironment 会在启动环境缺少某个键时,把主 workspace 的 .env/settings.env 中所有未被排除的键写入 process.env,而 QWEN_CODE_MEMORY_PROJECT_SCOPE 不在任何排除列表(PROJECT_ENV_HARDCODED_EXCLUSIONS、isLoaderEnvKey、DEFAULT_EXCLUDED_ENV_VARS)中——失败场景:在 .env 设置了 QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root 的仓库中,不传参数、不设启动环境变量直接运行 qwen serve → 该键在 runQwenServeImpl 快照之前进入 process.env,daemon 会把 git-root 冻结到每个 runtime(正是本 PR 要消除的共享),而启动日志记录 projectMemoryScopeSource=environment,把排查方向指向启动环境,实际值却来自 workspace 文件。相邻的 DEV 键已在硬编码排除列表中并有专门测试(run-qwen-serve.test.ts)。
建议修复:把 QWEN_CODE_MEMORY_PROJECT_SCOPE 加入 packages/cli/src/config/shared-env-keys.ts 的 PROJECT_ENV_HARDCODED_EXCLUSIONS,让所有项目 env 加载器都拒绝从 workspace env 文件读取该键;或者修正文档,说明当参数和启动环境都未设置时,serve 启动目录下的 workspace env 文件仍可为 scope 赋值。
— qwen3.8-max via Qwen Code /review (v0.21.8)
There was a problem hiding this comment.
已修复 + 验证证据:c6ed41cf6c;共享排除表回归 45/45、既有 full-loader 门控 probe 2/2 通过;ESLint、Prettier、git diff --check 通过。
| const resolvedMemoryProjectScope = | ||
| memoryProjectScopeValue.trim().toLowerCase() === 'workspace' | ||
| ? 'workspace' | ||
| : 'git-root'; |
There was a problem hiding this comment.
[Suggestion] The scope-normalization rule (trim + lowercase; only workspace opts in, everything else is git-root) is now implemented a second time here; the original is resolveWorkspaceProjectScope() in packages/core/src/memory/paths.ts, and nothing couples the two implementations — Failure scenario: any future change to the core rule (a new alias, different normalization, a different fallback) leaves this boot-log computation silently asserting a scope the runtimes don't apply — the exact diagnostic this log line was added for becomes wrong, and the divergence is invisible. Cost today: one decision rule maintained in two packages, with the runtime side authoritative and the daemon side log-only.
Suggested fix: extract the pure decision into packages/core/src/memory/scopes.ts — a zero-import leaf, already subpath-exported as @qwen-code/qwen-code-core/memoryScopes and already imported in this file — e.g. export function normalizeMemoryProjectScope(raw: string | undefined): MemoryProjectScope, and call it from both resolveWorkspaceProjectScope() and here; the warn-once side effect and the direct env read stay in paths.ts.
中文说明
scope 归一化规则(trim + 小写;仅 workspace 生效,其余一律 git-root)在这里被第二次实现;原始实现在 packages/core/src/memory/paths.ts 的 resolveWorkspaceProjectScope(),两处实现之间没有任何耦合——失败场景:未来对 core 规则的任何修改(新增别名、不同的归一化、不同的回退值)都会让这条启动日志的计算悄悄断言一个 runtime 实际并未应用的 scope——这条日志正是为排查而加的,届时它会给出错误信息,且分歧不可见。当前成本:一条判定规则分散在两个包中维护,runtime 侧才是权威,daemon 侧仅用于日志。
建议修复:把纯判定逻辑抽到 packages/core/src/memory/scopes.ts——它是零依赖叶子模块,已通过 @qwen-code/qwen-code-core/memoryScopes 子路径导出,且本文件已经引入——例如 export function normalizeMemoryProjectScope(raw: string | undefined): MemoryProjectScope,然后让 resolveWorkspaceProjectScope() 和这里都调用它;warn-once 副作用和直接读 env 的逻辑保留在 paths.ts。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| expect( | ||
| runtime.env.effectiveEnv?.['QWEN_CODE_MEMORY_PROJECT_SCOPE'], | ||
| ).toBe('git-root'); | ||
| ).toBe(expectedScope); |
There was a problem hiding this comment.
[Suggestion] No test pins the newly documented guarantee that a workspace .env/settings.env cannot override the daemon's frozen scope — the it.each workspaces are empty mkdtemp dirs with no .env file, so the always-inject × setRuntimeEnvIfUnset interaction is never exercised — Failure scenario: if a future change re-introduces conditional injection (key absent in the default case) or switches the overlay to override mode, a single workspace's .env containing QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root would silently flip that runtime back to shared git-root memory — recreating exactly the cross-workspace sharing this PR removes — while the whole suite stays green.
Suggested fix: add a case that writes a .env containing QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root into one registered workspace directory and asserts that runtime's effective value stays workspace.
中文说明
没有测试固化本文档新增的保证——workspace 的 .env/settings.env 无法覆盖 daemon 冻结的 scope——it.each 中的 workspace 是空的 mkdtemp 目录,没有 .env 文件,因此"始终注入 × setRuntimeEnvIfUnset(仅空位写入)"的交互从未被验证——失败场景:如果未来某个改动恢复了条件注入(默认情况下不注入该键),或把 overlay 改为覆盖模式,那么某个 workspace 的 .env 中的 QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root 就会悄悄把该 runtime 切回共享的 git-root 记忆——恰好重现本 PR 要消除的跨 workspace 共享——而整个测试套件仍是绿的。
建议修复:新增一个用例,向某个已注册的 workspace 目录写入包含 QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root 的 .env,并断言该 runtime 的有效值仍为 workspace。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| } | ||
| : {}), | ||
| }; | ||
| const baseEnv: NodeJS.ProcessEnv = { ...process.env }; |
There was a problem hiding this comment.
[Suggestion] Second seeding path for the same documented-guarantee violation (runtime-probe verified): the yargs fallback serve handler (commands/serve.ts:688-690) calls loadSettings(...) for the YOLO warning without skipLoadEnvironment, so loadEnvironment() writes QWEN_CODE_MEMORY_PROJECT_SCOPE from the primary workspace's .env/settings.env into process.env before this snapshot is taken — Failure scenario: qwen serve booted via an argv shape the fast-path parser rejects (fallback to full yargs), no scope exported, repo-committed .env sets git-root → the snapshot freezes git-root daemon-wide, the boot log misattributes projectMemoryScopeSource=environment, and the new isolation default is silently disabled. Blank-export asymmetry: the fast path's Object.hasOwn guard blocks .env seeding when the variable is exported blank, but this path's isEffectivelyUnset treats '' as unset and lets the .env value win — identical inputs produce different scopes per entry path. Fixing only loadServeFastPathEnvironment (see the separate comment on the docs page) leaves this path open.
Suggested fix: pass skipLoadEnvironment: true to the boot-time loadSettings call in the serve handler (it only reads merged.tools for the YOLO predicate), and/or add the key to PROJECT_ENV_HARDCODED_EXCLUSIONS, which closes both this path and the fast path in one place.
中文说明
同一个"文档保证被破坏"问题的第二条注入路径(已通过运行时探针验证):yargs 回退的 serve handler(commands/serve.ts:688-690)为了 YOLO 警告调用 loadSettings(...) 时没有传 skipLoadEnvironment,因此 loadEnvironment() 会在本快照之前,把主 workspace 的 .env/settings.env 中的 QWEN_CODE_MEMORY_PROJECT_SCOPE 写入 process.env——失败场景:通过快速路径解析器拒绝的 argv 形式启动 qwen serve(回退到完整 yargs),未导出 scope,仓库提交的 .env 设置了 git-root → 快照把 git-root 冻结到整个 daemon,启动日志错误地记录 projectMemoryScopeSource=environment,新的隔离默认值被悄悄禁用。空白导出的不对称:快速路径的 Object.hasOwn 守卫在变量被导出为空时阻止 .env 注入,而本路径的 isEffectivelyUnset 把 '' 视为未设置、让 .env 的值生效——相同输入在不同入口路径下产生不同的 scope。只修 loadServeFastPathEnvironment(见文档页上的另一条评论)会留下这条路径。
建议修复:给 serve handler 启动时的 loadSettings 调用传入 skipLoadEnvironment: true(它只为 YOLO 判断读取 merged.tools),和/或把该键加入 PROJECT_ENV_HARDCODED_EXCLUSIONS,一次性同时关闭这条路径和快速路径。
— qwen3.8-max via Qwen Code /review (v0.21.8)
There was a problem hiding this comment.
已修复 + 验证证据:c6ed41cf6c;共享排除表回归 45/45、既有 full-loader 门控 probe 2/2 通过;ESLint、Prettier、git diff --check 通过。
| await startServeHandlerWithArgs( | ||
| '--no-web --memory-project-scope workspace', | ||
| ); | ||
| await startServeHandlerWithArgs('--no-web --memory-project-scope git-root'); |
There was a problem hiding this comment.
[Suggestion] No test pins the new default chain at the yargs CLI boundary: flag omitted → memoryProjectScope absent from the options → daemon-side workspace default. The only handler-level test now exercises an explicit non-default flag (this one), and the parser tests also only pass explicit values; the fast-path parser is pinned by an exact-shape assertion, but the yargs boundary is not — Failure scenario: a future change reintroducing a default at the CLI layer (e.g. default: 'git-root' on the yargs option or argv['memory-project-scope'] ?? 'git-root' in the handler spread) would make every flag-less qwen serve keep legacy git-root partitioning while the docs promise workspace isolation; the it.each cases call runQwenServe directly and would stay green, and this suite never asserts the option's absence, so the regression ships green.
Suggested fix: add a handler test asserting startServeHandlerWithArgs('--no-web') calls mockRunQwenServe with an options object that does not contain memoryProjectScope.
中文说明
没有测试在 yargs CLI 边界固化新的默认链:不传参数 → options 中不含 memoryProjectScope → daemon 侧默认 workspace。唯一的 handler 级测试现在只验证显式传入非默认值(即本测试),parser 测试也只传显式值;快速路径 parser 有精确形状断言固化,但 yargs 边界没有——失败场景:未来在 CLI 层重新引入默认值(例如给 yargs 选项加 default: 'git-root',或在 handler 的展开中写 argv['memory-project-scope'] ?? 'git-root')会让每个不带参数的 qwen serve 继续使用旧的 git-root 分区,而文档承诺的是 workspace 隔离;it.each 用例直接调用 runQwenServe,仍是绿的,且本套件从未断言该选项的缺失,回归会带着全绿上线。
建议修复:新增 handler 测试,断言 startServeHandlerWithArgs('--no-web') 调用 mockRunQwenServe 时 options 对象不包含 memoryProjectScope。
— qwen3.8-max via Qwen Code /review (v0.21.8)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): Context: this PR makes workspace-scoped project memory th...: none — all planned checks completed within budget.; Context: reverse audit round 2 for a review of PR #8856 (...: none — all checks above were completed within budget.; Context: reverse audit round 1 for a review of PR #8856 (...: none — all planned checks completed within budget..
中文说明
已审查。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):Context: this PR makes workspace-scoped project memory th...:none — all planned checks completed within budget.;Context: reverse audit round 2 for a review of PR #8856 (...:none — all checks above were completed within budget.;Context: reverse audit round 1 for a review of PR #8856 (...:none — all planned checks completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| // Project memory routing is frozen daemon-wide before workspace env files | ||
| // load, so only the operator's launch environment or CLI flag may set it. | ||
| 'QWEN_CODE_MEMORY_PROJECT_SCOPE', |
There was a problem hiding this comment.
[Suggestion] The new hardcoded exclusion silently rejects workspace .env/settings.env scope pins at every entry point (probe-verified at HEAD; reverting this hunk flips every observation). Two surfaces: (1) daemon — rejected entries are never reported (reportRejectedLoaderKeys filters on loader-class keys only, and every isHardcodedProjectEnvExclusion use site is a silent skip), so the boot log reads projectMemoryScopeSource=default while the on-disk workspace config says otherwise, and nothing names the ignored file entry; (2) standalone — the pin the pre-change docs recommended ("pin the scope in the workspace .env or settings.env", a sentence this PR removes from docs/users/qwen-serve.md) silently repartitions project memory back to git-root with no warning anywhere: the key is not loader-class, standalone has no boot log and no flag alternative, and the home-.env exemption does not mitigate — home settings.env is rejected unconditionally. docs/design/auto-memory/memory-system.md still presents the variable as a plain env override without stating the new restriction. — Failure scenario: an operator who pinned QWEN_CODE_MEMORY_PROJECT_SCOPE in a workspace env file per the pre-change docs upgrades → the daemon freezes workspace (boot log claims source=default against the on-disk config) and a standalone qwen in the same repo silently falls back to git-root partitioning → workspace-partitioned project memory becomes invisible with no migration and no diagnostic that names the ignored entry.
Suggested fix: emit a one-time rejection notice for hardcoded-excluded keys present in workspace env files (mirroring reportRejectedLoaderKeys, covering the standalone loaders as well, routed to the durable daemon log for serve-managed loads), and state in docs/design/auto-memory/memory-system.md that only the process launch environment (or --memory-project-scope for the daemon) may set this variable.
中文说明
新增的硬编码排除项会在所有入口静默拒绝 workspace .env/settings.env 中的 scope 固定(已在 HEAD 用探针验证;回滚本 hunk 后所有观测结果翻转)。两个层面:(1) daemon——被拒绝的条目不会有任何上报(reportRejectedLoaderKeys 只过滤 loader 类键,且每个 isHardcodedProjectEnvExclusion 使用点都是静默跳过),因此启动日志显示 projectMemoryScopeSource=default,与磁盘上的 workspace 配置矛盾,且没有任何日志指出被忽略的文件条目;(2) standalone——旧文档推荐的固定方式("把 scope 固定在 workspace 的 .env 或 settings.env",本 PR 已从 docs/users/qwen-serve.md 删除该句)会静默把项目记忆重新分区回 git-root,全程无告警:该键不是 loader 类,standalone 没有启动日志也没有等价的命令行参数,且 home .env 豁免并不足以补救——home settings.env 会被无条件拒绝。docs/design/auto-memory/memory-system.md 仍把该变量描述为普通环境变量覆盖,未说明新限制。——失败场景:按旧文档把 QWEN_CODE_MEMORY_PROJECT_SCOPE 固定在 workspace env 文件中的运维人员升级后 → daemon 冻结为 workspace(启动日志却声称 source=default,与磁盘配置矛盾),同一仓库下的 standalone qwen 静默回退到 git-root 分区 → 按 workspace 分区的项目记忆变得不可见,没有迁移,也没有任何指出被忽略条目的诊断。
建议修复:对 workspace env 文件中出现的硬编码排除键发出一次性拒绝通知(仿照 reportRejectedLoaderKeys,同时覆盖 standalone 加载器,serve 管理的加载路由到持久 daemon 日志),并在 docs/design/auto-memory/memory-system.md 中说明只有进程启动环境(或 daemon 的 --memory-project-scope)可以设置该变量。
— qwen3.8-max via Qwen Code /review (v0.21.9)
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 76 passed · 0 failed · 76 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:76 通过 · 0 失败 · 76 总计 Verification report<!-- qwen-triage:verify --> Sandboxed verification: ✅ passed — merge-ready (agent verdict) - follow-up round at head Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 76 passed · 0 failed · 76 total 中文 — 判定:✅ 通过 · 可合入(agent 判定,第二轮)
Previous-round finding status (follow-up round)Previous round verified head
Central claim and A/B at the new headCentral claim (unchanged): a The oracle is the per-runtime
Downstream semantics of the raw passthrough cells are core behavior, unchanged by this PR ( Mutation matrix (8 killed, 1 survivor classified)Each mutant edits exactly one guard in a scratch worktree (
¹ M6's env-preserved case staying green is the round-1 finding-2 blind spot, re-measured and deliberate: the mutant is still killed by the other four cases. ² M9 swaps the Reviewer Test Plan walk-through
Findings (non-blocking)
Not covered
MethodologyEnvironment: CI verify container ( — Qwen Code · sandboxed verification Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
Local real-stack verification — PR #8856I rebuilt this PR locally and exercised it on a real Verdict: behaves exactly as the PR describes. Recommend merge. Three non-blocking notes at the bottom. How it was verified
Four independent observations per scenario, so no single instrument can carry the result on its own:
1. The headline change — default scopeWith nothing configured, the merge-base puts both workspaces into one git-root-keyed store; the PR head gives each workspace its own. Confirmed by all four probes simultaneously. 2. Full precedence matrix
Rows 2/3 confirm the stated precedence; row 8 confirms the standalone CLI default is untouched. In every scenario 3. Upgrade impact, measured rather than assumedI seeded a project memory under the legacy git-root key before boot (marker
So the breaking change is exactly as documented: the legacy store is never deleted, only unreferenced, and the documented opt-out restores it verbatim. 4. The scope key is now operator-ownedOn the merge-base, a repo-supplied Tests, lint, types
Non-blocking notes
Scope of this verificationmacOS only; the model was a deterministic local stub, so no real-provider behaviour is covered. Windows and Linux remain unverified, matching the PR's own claim table. 中文版PR #8856 本地真实环境验证我在本地重新构建了这个 PR,并在真实的 结论:行为与 PR 描述完全一致,建议合入。 文末有三条不阻塞的建议。 验证方式
每个场景取四个互相独立的判据,避免单一观测口径决定结论:
1. 核心改动:默认 scopemerge-base 在无配置时把两个 workspace 归到同一个 git-root key 的存储;PR head 让每个 workspace 拥有各自独立的存储。四个判据同时确认。 2. 完整优先级矩阵
第 2、3 行确认了声明的优先级;第 8 行确认 standalone CLI 默认值没被动。所有场景中 daemon 自身的 3. 升级影响:实测而非推断启动前我在旧的 git-root key 下预置了一条项目记忆(标记
破坏性变化与文档描述完全一致:旧存储不会被删除,只是不再被引用;文档给出的回退开关能原样恢复。 4. scope 变成 operator 独占在 merge-base 上,仓库自带的 测试、lint、类型
不阻塞的建议
本次验证的边界仅覆盖 macOS;模型是确定性的本地桩,未覆盖真实 provider 行为。Windows 与 Linux 仍未验证,与 PR 自己的声明表一致。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and the unit suite was not run on Windows locally (the platform relevant to the Needs Human Review env-case finding).
Not explored to full depth (tool budget reached): Context: this PR makes workspace-scoped project memory th...: did not conclusively trace whether ACP child stderr is captured into the durable daemon log (checked packages/cli/src spawn/stderr paths only, no pipe found) …; Context: reverse audit round 2 for a review of PR #8856 (...: none (finished well under the tool ceiling)..
中文说明
已审查。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and the unit suite was not run on Windows locally (the platform relevant to the Needs Human Review env-case finding)。
未探索到全部深度(达到工具调用预算):Context: this PR makes workspace-scoped project memory th...:did not conclusively trace whether ACP child stderr is captured into the durable daemon log (checked packages/cli/src spawn/stderr paths only, no pipe found) …;Context: reverse audit round 2 for a review of PR #8856 (...:none (finished well under the tool ceiling).。
— qwen3.8-max via Qwen Code /review (v0.21.9)
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 82 passed · 0 failed · 82 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:82 通过 · 0 失败 · 82 总计 Verification reportSandboxed verification: ✅ passed — merge-ready (agent verdict) — follow-up round 3 at head Scripted assertions: 82 passed · 0 failed · 82 total (58 A/B + 24 mutation matrix) 中文 — 判定:✅ 通过 · 可合入(agent 判定,第三轮)
Previous-round finding status (follow-up round)Round 2 verified head
Central claim and A/B at the new headCentral claim (unchanged): a The oracle is the per-runtime
Reviewer Test Plan walk-through: (1) two registered dirs, no config → head cell Mutation matrix (8 killed, 1 survivor re-classified)Each mutant edits exactly one guard in a scratch worktree at HEAD, runs the 10 targeted tests (the 6 scope cases, the daemon-logger test,
Findings (non-blocking, carried over — all re-measured)
No new findings this round: the merge commit touched none of the PR's surface, and no regressions appeared in any re-measurement. Not covered
MethodologyEnvironment: CI verify container ( — Qwen Code · sandboxed verification Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
Released in v0.21.11. |
















What this PR does
Makes exact-workspace project-memory isolation the default for every runtime managed by
qwen serve. Explicit CLI configuration still wins over the launch environment, and an explicitgit-rootselection remains available as a legacy migration path.Updates the user-facing help and memory documentation to distinguish the daemon default from the unchanged standalone CLI behavior and to explain that existing Git-root memory is not migrated automatically.
Why it's needed
The daemon already creates and routes a distinct runtime for each registered workspace, but project memory previously stayed shared at the Git root unless operators supplied an opt-in flag. That made the normal multi-workspace daemon configuration share one runtime-owned resource across otherwise isolated workspaces.
Reviewer Test Plan
How to verify
qwen servewith two registered directories under the same Git repository and omit both memory-project-scope settings. Confirm every runtime receivesworkspaceand project-memory operations resolve independently for the two directories.git-rootwithout passing the option. Confirm every runtime retains the legacy shared scope.workspaceand pass--memory-project-scope git-root. Confirm the option wins for every runtime and the daemon does not mutate its launch environment.qweninvocation still uses its existing Git-root default.Evidence (Before & After)
Before: the new default-isolation regression test failed because the runtime environment contained no project-memory scope (
expected undefined to be "workspace").After: the default, launch-environment compatibility, and CLI-over-environment cases pass for both primary and secondary runtimes. The complete related suite passes 298/298 tests; the repository build, typecheck, focused ESLint, and Prettier checks also pass.
Tested on
Environment (optional)
macOS with Node.js 24.14.1; package-specific Vitest coverage plus repository build and typecheck.
Risk & Scope
--memory-project-scope git-rootor setQWEN_CODE_MEMORY_PROJECT_SCOPE=git-rootto retain the previous view during migration.Linked Issues
Closes #8854
中文说明
本 PR 做了什么
让
qwen serve管理的每个 runtime 默认按精确 workspace 隔离项目记忆。显式 CLI 配置仍优先于启动环境变量,同时保留显式git-root,作为旧存储迁移期间的兼容入口。同步更新用户帮助和记忆文档,明确 daemon 默认值与保持不变的 standalone CLI 行为,并说明不会自动迁移已有的 Git-root 记忆。
为什么需要
daemon 已经为每个注册 workspace 创建并路由独立 runtime,但项目记忆此前仍默认按 Git 根目录共享,只有传入 opt-in 参数才会隔离。这导致常规多 workspace daemon 配置中,一个由 runtime 管理的资源仍跨越了其他资源已经遵守的 workspace 边界。
Reviewer 测试计划
如何验证
qwen serve,不要设置任何 memory-project-scope 配置。确认每个 runtime 都收到workspace,两个目录的项目记忆操作分别解析到独立存储。git-root,不传 CLI 选项。确认每个 runtime 都保留旧的共享 scope。workspace,同时传入--memory-project-scope git-root。确认 CLI 对每个 runtime 生效且 daemon 不修改启动环境。qwen仍使用原有的 Git-root 默认值。前后证据
改动前:新增的默认隔离回归测试失败,因为 runtime 环境中没有项目记忆 scope(
expected undefined to be "workspace")。改动后:默认值、启动环境兼容、CLI 覆盖环境变量三个场景都在 primary 和 secondary runtime 上通过。完整相关测试 298/298 通过;仓库 build、typecheck、聚焦 ESLint 和 Prettier 检查也全部通过。
测试平台
环境(可选)
macOS,Node.js 24.14.1;执行 package 级 Vitest、仓库 build 和 typecheck。
风险与范围
--memory-project-scope git-root或设置QWEN_CODE_MEMORY_PROJECT_SCOPE=git-root保留旧视图。关联 Issue
Closes #8854