Skip to content

fix(cli): resolve session-management settings per request, not from the stale this.settings cache - #10095

Open
tomsen02 wants to merge 3 commits into
QwenLM:mainfrom
tomsen02:fix/session-mgmt-stale-settings
Open

fix(cli): resolve session-management settings per request, not from the stale this.settings cache#10095
tomsen02 wants to merge 3 commits into
QwenLM:mainfrom
tomsen02:fix/session-mgmt-stale-settings

Conversation

@tomsen02

@tomsen02 tomsen02 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What this PR does

In a multi-workspace daemon, three session-management handlers — unstable_listSessions, deleteSession, and the dead-session renameSession path — derived the runtime output root from this.settings, the agent-level "latest loaded" cache, instead of the request's own workspace settings. This PR makes all three resolve settings per request with loadSettingsCached(cwd), the same pattern the sessionTranscript and session-restore handlers in the same file already use. this.settings stays in place for the genuinely agent-level readers, per #6292's design.

Why it's needed

runWithAcpRuntimeOutputDir derives the runtime base dir from settings.merged.advanced.runtimeOutputDir, and SessionService captures it at construction, so the chats directory it operates on is <runtimeBaseDir>/projects/<cwd>/chats/. The cache is refreshed by any workspace's activity, so after workspace B's session activity, a request for workspace A (whose advanced.runtimeOutputDir differs) operated on B's runtime root: listSessions returned an empty/foreign list for A, deleteSession silently returned success:false for a session that exists (or could remove a stale same-id copy under the wrong root and report success while the live session survives), and dead-session renameSession mis-targeted the same way. Not a transient race — the misrouting persists until A's settings happen to be reloaded.

#6292 fixed exactly this class on the session-creation entry points and deliberately kept this.settings for "agent-level readers … which keeps their behavior unchanged"; these three handlers were misfiled in that bucket, but they are not workspace-neutral — they derive the runtime root. (#4709/#4715 fixed the auto-memory variant of the same runtime-dir misrouting.)

Reviewer Test Plan

How to verify

  1. Run the regression tests: cd packages/cli && npx vitest run src/acp-integration/acpAgent.test.ts → 486/486. The three new tests (resolves deleteSession settings per request…, resolves dead-session renameSession settings per request…, resolves unstable_listSessions settings per request…) boot the agent with one settings object, re-point loadSettings to a different per-request object, invoke each handler, and assert the per-request instance (not the boot-time cache) reaches runWithAcpRuntimeOutputDir.
  2. Mutation check: revert any one call site back to this.settings — its test fails ("expected … to be … // Object.is equality"); the shipped code passes all three.
  3. Optional end-to-end reproduction: the probe in ACP session-management handlers resolve the runtime output dir from stale this.settings, misrouting list/delete/rename across workspaces #10094 replays the handlers' exact call shape with production Storage/SessionService/runWithAcpRuntimeOutputDir on a real filesystem (no mocks): seed a session for workspace A under A's configured advanced.runtimeOutputDir, then run find/remove once with workspace B's settings (the stale-cache shape) and once with A's own.

Evidence (Before & After)

Probe output (from #10094), real filesystem, production modules:

Before (stale B settings, A's cwd — the shape this PR removes):
  findSessionIdIgnoringCase → undefined, removeSession → false   # A's session invisible & undeletable

After (per-request A settings, same cwd — the shape this PR ships):
  findSessionIdIgnoringCase → <uuid>,    removeSession → true    # found and deleted

The control arm runs after the bug arm, proving the session file had survived the misrouted delete. No user-visible TUI change.

Tested on

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

Environment (optional)

Unit tests via vitest from packages/cli; probe via tsx against the built @qwen-code/qwen-code-core on macOS (Darwin 25.4.0, Node 22).

Risk & Scope

  • Main risk or tradeoff: none expected — no behavior change for single-workspace daemons (same settings either way) or for workspaces without advanced.runtimeOutputDir (same default root either way); loadSettingsCached is the same per-cwd cache the neighboring handlers already use, so no extra settings I/O on repeated calls.
  • Not validated / out of scope: other this.settings readers that are genuinely agent-level (auth persistence, fastModel, folder trust, language) — deliberately untouched per fix(acp): pass per-session settings explicitly instead of racing on this.settings #6292's design; a full-daemon two-workspace E2E (the module-level probe above covers the mechanism).
  • Breaking changes / migration notes: none.

Linked Issues

Fixes #10094.

Follow-up to #6292; same misrouting family as #4709/#4715.

中文说明

本 PR 做了什么:多 workspace daemon 下,三个会话管理入口(unstable_listSessionsdeleteSession、dead-session 的 renameSession)用 agent 级"最后加载"缓存 this.settings 解析 runtime 输出根目录。本 PR 让三者改为按请求 loadSettingsCached(cwd) 解析——与同文件 sessionTranscript 等入口已有的模式一致;真正 agent 级的 this.settings 读者按 #6292 的设计保持不变。

为什么需要runWithAcpRuntimeOutputDirsettings.merged.advanced.runtimeOutputDir 派生 runtime 根目录,SessionService 在构造时捕获它。缓存会被任何 workspace 的活动刷新,因此 workspace B 活动后,对 workspace A 的请求会操作 B 的 runtime 根:列表对 A 返回空/异 workspace 结果;删除对存在的会话静默返回 success:false(或删掉错误根下的同 id 陈旧副本并报告成功而真会话幸存);重命名同样错靶。这不是瞬时竞态——错误路由持续到 A 的设置恰好再次被加载。#6292 已在会话创建入口修复同类问题并刻意为"agent 级读者"保留 this.settings;这三个入口被误归入该桶,但它们并非 workspace 无关。(#4709/#4715 修复过同家族的 auto-memory 变体。)

验证方法:1) cd packages/cli && npx vitest run src/acp-integration/acpAgent.test.ts → 486/486,三个新回归测试分别断言各入口把按请求的 settings 实例(而非启动时缓存)传给 runWithAcpRuntimeOutputDir;2) 变异检查:任一调用点改回 this.settings,对应测试变红;3) 可选端到端复现见 #10094 的真实文件系统探针(生产模块、无 mock)。

Before/After 证据:Before(B 的过期设置 + A 的 cwd)found=undefined, removed=false(A 的会话不可见且不可删);After(按请求的 A 设置,同一 cwd)found=<uuid>, removed=true。对照臂在 bug 臂之后运行,证明会话文件在错误路由的删除下幸存。无用户可见 TUI 变化。

测试平台:macOS 已测(Darwin 25.4.0,Node 22);Windows/Linux 未本地测试,交由 CI。

风险与范围:单 workspace daemon 与未配置 advanced.runtimeOutputDir 的 workspace 行为不变;loadSettingsCached 为按 cwd 缓存,无额外 I/O。未验证:真正 agent 级的其它 this.settings 读者(按 #6292 设计刻意不动);完整双 workspace daemon E2E(模块级探针已覆盖机制)。无破坏性变更。

关联 IssueFixes #10094#6292 的后续;与 #4709/#4715 同一错误路由家族。

🤖 Generated with Claude Code

…he stale this.settings cache

In a multi-workspace daemon, unstable_listSessions, deleteSession and the
dead-session renameSession path derived the runtime output root from
this.settings — the agent-level "latest loaded" cache — so after another
workspace's activity they operated on the wrong runtime root: listSessions
returned an empty/foreign list, deleteSession silently no-oped (or could
remove a stale same-id copy under the wrong root), and renameSession
mis-targeted the same way. The misrouting persists until the requesting
workspace's settings happen to be reloaded.

Load settings per request with loadSettingsCached(cwd), mirroring the
pattern the sessionTranscript and session-restore handlers in the same
file already use. Follow-up to QwenLM#6292, which fixed this class on the
session-creation entry points and deliberately left this.settings in
place for workspace-neutral agent-level readers — these three handlers
were misfiled in that bucket.

Fixes QwenLM#10094.

Verification: three regression tests assert each handler passes the
per-request settings instance (mutation-verified: reverting a call site
to this.settings fails its test); acpAgent.test.ts 486/486; typecheck,
ESLint and Prettier clean. Real-filesystem probe in the issue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot qwen-code-ci-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.

Thanks for the PR, @tomsen02! The write-up is thorough — the stale-cache mechanism is clearly explained, and issue #10094 carries a real-filesystem reproduction (BUG arm vs CONTROL arm), which is exactly the kind of evidence this gate looks for.

One thing blocks it at the gate: the PR body doesn't follow the PR template. Two gaps:

  • The ## Reviewer Test Plan section is missing, including its ### How to verify, ### Evidence (Before & After), and ### Tested on subsections. Your ## Reproduction and ## Verification content is good — please move it into those subsections: the probe output from #10094 (the found=undefined, removed=false vs found=<uuid>, removed=true arms) belongs under Evidence (Before & After), the regression-test/mutation-verification notes under How to verify, and please fill in the Tested on OS table.
  • The ## Linked Issues heading is missing — Fixes #10094 is already in the body, it just needs to live under that heading.

Everything else is present (## What this PR does, ## Why it's needed, ## Risk & Scope, and the bilingual summary). Once the template sections are in place, re-run the review and it will continue.

中文说明

感谢贡献,@tomsen02!PR 描述写得很扎实——过期缓存机制讲得很清楚,issue #10094 里还有真实文件系统的复现(BUG 臂 vs 对照臂),正是评审所需要的证据。

目前卡在模板检查这一步:PR 正文没有遵循 PR 模板,有两处缺口:

  • 缺少 ## Reviewer Test Plan 部分,包括其 ### How to verify### Evidence (Before & After)### Tested on 子节。现有的 ## Reproduction## Verification 内容很好,请把它们移入对应子节:#10094 中的探针输出(found=undefined, removed=falsefound=<uuid>, removed=true 两臂)放进 Evidence (Before & After),回归测试/变异验证说明放进 How to verify,并请填写 Tested on 的操作系统表格。
  • 缺少 ## Linked Issues 标题——Fixes #10094 已经写在正文里,只需移到该标题下。

其余部分都已具备(## What this PR does## Why it's needed## Risk & Scope 以及中文摘要)。补齐模板各节后重新触发评审即可继续。

Qwen Code · qwen3.8-max

@tomsen02

Copy link
Copy Markdown
Contributor Author

PR body restructured to the template: Reviewer Test Plan (How to verify / Evidence Before & After / Tested on / Environment) and Linked Issues are now in place, with the probe's before/after arms under Evidence and the regression-test + mutation notes under How to verify.

@qwen-code /triage

The round-1 gate blocked solely on PR-body template structure; the body
now carries Reviewer Test Plan (How to verify / Evidence / Tested on)
and Linked Issues. No code changes.
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Aug 26, 2026

@qwen-code-ci-bot qwen-code-ci-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.

Reviewed — no blockers. Suggestions are inline.

中文说明

已审查——无阻断问题。 建议见行内评论。

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

Comment on lines +16338 to +16341
expect(runWithAcpRuntimeOutputDir).toHaveBeenCalledTimes(1);
expect(vi.mocked(runWithAcpRuntimeOutputDir).mock.calls[0]![0]).toBe(
perRequestSettings,
);

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.

[Suggestion] The three new regression tests pin the settings object but not the per-request cwd: the mocked loadSettings (and the passthrough loadSettingsCached) return perRequestSettings for any argument, and runWithAcpRuntimeOutputDir's second argument is never checked — the deleteSession and unstable_listSessions tests have no SessionService-cwd assertion either. A future regression that resolves settings from the wrong directory (e.g. loadSettingsCached(this.config.getTargetDir())), or drifts the routed cwd to the boot-workspace value, would re-introduce exactly the multi-workspace misrouting this PR fixes — while all three tests stay green. Verified by mutation probe at the reviewed commit: reverting the settings key or the routing cwd to the boot dir keeps all three tests passing (Tests 3 passed), and adding the assertions below turns both mutants red (Tests 3 failed). Pin the key here, and add the same two assertions to the renameSession and unstable_listSessions tests:

Suggested change
expect(runWithAcpRuntimeOutputDir).toHaveBeenCalledTimes(1);
expect(vi.mocked(runWithAcpRuntimeOutputDir).mock.calls[0]![0]).toBe(
perRequestSettings,
);
expect(runWithAcpRuntimeOutputDir).toHaveBeenCalledTimes(1);
expect(vi.mocked(runWithAcpRuntimeOutputDir).mock.calls[0]![0]).toBe(
perRequestSettings,
);
expect(loadSettings).toHaveBeenCalledWith('/tmp/workspace-a');
expect(vi.mocked(runWithAcpRuntimeOutputDir).mock.calls[0]![1]).toBe(
'/tmp/workspace-a',
);
中文说明

三个新回归测试只固定了 settings 对象,没有固定按请求的 cwd:mock 的 loadSettings(以及直通式的 loadSettingsCached)对任何参数都返回 perRequestSettings,且 runWithAcpRuntimeOutputDir 的第二个参数从未被断言——deleteSession 与 unstable_listSessions 测试也没有对 SessionService 的 cwd 断言。未来若有回归把 settings 解析到错误目录(例如 loadSettingsCached(this.config.getTargetDir())),或把路由的 cwd 漂移到启动时 workspace 的值,将重新引入本 PR 所修复的多 workspace 错误路由——而三个测试仍全部为绿。已在被审提交上用变异探针验证:把 settings 键或路由 cwd 改回启动目录,三个测试依然通过(Tests 3 passed);加入上述两条断言后,两种变异均变红(Tests 3 failed)。请在此处固定键值,并在 renameSession 与 unstable_listSessions 测试中加入相同的两条断言。

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

… tests

Round-1 R1-1: the three regression tests pinned the settings object but
not the directory it was resolved from or routed to — a regression that
resolves settings from the boot workspace's dir (or drifts the routed
cwd) would pass all three. Each test now also asserts
loadSettings('/tmp/workspace-a') and runWithAcpRuntimeOutputDir's cwd
argument, per the review's suggestion. Mutation-verified: rerouting
deleteSession through this.config.getTargetDir() fails its test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tomsen02

Copy link
Copy Markdown
Contributor Author

R1-1 addressed in addeb56422 (test-only): all three regression tests now also pin the per-request directory — loadSettings must be called with the request's cwd and runWithAcpRuntimeOutputDir must receive that same cwd as its routing argument, exactly as the inline suggestion sketched.

Mutation-verified locally: rerouting deleteSession's settings/cwd through this.config.getTargetDir() fails its test; real code passes 486/486.

@tomsen02

Copy link
Copy Markdown
Contributor Author

Status note: review converged (round on addeb56422 posted zero blocking findings, CI 27/27 green); the only gate is the pre-restructure template review still standing from before the body was fixed. Sister PR #9930 by the same author merged this morning — this one follows the same playbook and is ready for a re-triage whenever convenient. 谢谢!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ACP session-management handlers resolve the runtime output dir from stale this.settings, misrouting list/delete/rename across workspaces

2 participants