Skip to content

fix(cli): route turn-index reads through runtime-root pin - #11036

Merged
yiliang114 merged 2 commits into
mainfrom
codex/fix-turn-index-runtime-root-pin
Sep 5, 2026
Merged

fix(cli): route turn-index reads through runtime-root pin#11036
yiliang114 merged 2 commits into
mainfrom
codex/fix-turn-index-runtime-root-pin

Conversation

@yiliang114

Copy link
Copy Markdown
Collaborator

What this PR does

Routes the session turn-index reader through the existing per-request runtime-root helper. The helper performs the same cwd-based settings lookup and delegates to the same runtime context operation as the previous inline code.

Why it's needed

PR CI job 101103789058 deterministically failed the runtime-root choke-point test three times. #10751 added the turn-index reader after #10988's branch point; #10988 later added the invariant that caller-supplied-cwd handlers must use the shared helper. Both PRs passed independently, but the stale-base merge left the concurrently added reader outside the helper.

Reviewer Test Plan

How to verify

Confirm that the turn-index path still flushes the active recording when required, reads the same cwd and pagination arguments, and preserves the existing error mapping. Confirm that the source-level runtime-root invariant sees only the shared helper's permitted delegation.

Evidence (Before & After)

Before: the linked CI job reported a second direct runWithAcpRuntimeOutputDir identifier at the turn-index reader and failed 1 of 28,585 CLI tests.

After: replaying the test's TypeScript AST walk reports only 4595: return runWithAcpRuntimeOutputDir(settings, cwd, operation);. git diff --check and Prettier pass.

Tested on

OS Status
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

Environment (optional)

Static AST and formatting checks only.

Risk & Scope

  • Main risk or tradeoff: Minimal; the same cwd, callback, and Promise now pass through an existing behavior-equivalent helper.
  • Not validated / out of scope: No build or broad test suite was run.
  • Breaking changes / migration notes: None.

Linked Issues

Follow-up to #11033 and #11030.

中文说明

本 PR 做了什么

让 session turn-index reader 经过现有的 per-request runtime-root helper。该 helper 会执行与原内联代码相同的基于 cwd 的 settings 查找,并委托给相同的 runtime context 操作。

为什么需要

PR CI job 101103789058 中的 runtime-root choke-point 测试连续三次确定性失败。#10751#10988 的分支基线之后新增了 turn-index reader;#10988 随后加入 caller-supplied-cwd handler 必须使用共享 helper 的约束。两个 PR 独立运行时都通过,但陈旧基线合并使并发新增的 reader 留在了 helper 之外。

Reviewer Test Plan

如何验证

确认 turn-index 路径仍会在需要时 flush 活跃记录,使用相同的 cwd 和分页参数读取,并保留现有错误映射。确认源码级 runtime-root 约束只发现共享 helper 中唯一允许的委托。

证据(修改前后)

修改前:链接中的 CI job 在 turn-index reader 处发现第二个直接 runWithAcpRuntimeOutputDir 标识符,28,585 个 CLI 测试中有 1 个失败。

修改后:复刻该测试的 TypeScript AST 遍历后,只报告 4595: return runWithAcpRuntimeOutputDir(settings, cwd, operation);git diff --check 和 Prettier 均通过。

测试平台

OS 状态
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

环境(可选)

仅执行静态 AST 与格式检查。

风险与范围

  • 主要风险或取舍:很小;相同的 cwd、callback 和 Promise 现在经过一个已有且行为等价的 helper。
  • 未验证 / 不在范围内:未运行 build 或大范围测试套件。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

#11033#11030 的 follow-up。

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Deferred approval withheld — 1 PR CI workflow run(s) on 40ae7cf did not finish green; see the updated table in the Stage 2 comment. Re-run @qwen-code /triage after fixes. finalize run

⚠️ 延迟审批已搁置 —— 40ae7cf 有 1 个 PR CI workflow 未以绿色完成,详见 Stage 2 评论中已更新的表格。修复后可重新运行 @qwen-code /triage查看 finalize 运行

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, not theoretical — and it is red on main right now. I checked the base commit this PR sits on (e281bd1d, the #11033 merge) and it carries both halves of the failure: the runtime-root choke-point test in acpAgent.test.ts, and the second direct runWithAcpRuntimeOutputDir mention at acpAgent.ts:9200 inside the sessionTurnIndex handler. The job you linked (101103789058, Test (ubuntu-latest, Node 22.x)) confirms it — failed three times via retry x2, reporting mentions at 4595 and 9200 against an expectation of exactly one, with Tests 1 failed | 28494 passed | 90 skipped (28585). That matches your description precisely.

Direction: aligned. This restores an invariant #10095 introduced for a real bug class — handlers composing the runtime-root routing by hand and pinning another workspace's root via the process-wide this.settings cache. The stale-base story holds up: #10751 added the turn-index reader before #10988 landed the invariant, so each passed on its own and the merge left the concurrently added reader outside the helper. Nothing about the product surface moves. CHANGELOG: no direct reference, but the area is relevant — this is ACP serve wiring, not a user-facing behavior question.

Size: Stage 0 not applicable — packages/cli/src/acp-integration/ is not one of the protected core paths, and the change stays inside a single package. 9 production lines (6 added, 3 deleted), 0 test lines, 0 generated/schema lines, 1 file.

Approach: this is what I would have written. The per-request tier is the right one — the handler serves a caller-supplied cwd, so runWithPinnedRuntimeBaseDirForRequest should resolve the settings itself instead of the handler making that call. It also lands on exactly the shape the sibling sessionTranscript and settled-turn handlers already use (hoist the operation into a named local, hand it to the helper), so the file gets more consistent rather than less. Nothing to cut, and no unrelated edits riding along.

Risk: Stage 1e matched packages/cli/src/acp-integration/acpAgent.ts, so I reviewed at elevated depth and I'm requiring this PR's own CI evidence before approval rather than reasoning about what the test will do. The decisive check (Test (ubuntu-latest, Node 22.x)) is still in flight at the time of writing — see the Stage 2 comment.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 是已观测到的问题,不是理论性加固——而且 main 现在就是红的。我核对了本 PR 的基线提交(e281bd1d,即 #11033 的合并),它同时包含失败的两半:acpAgent.test.ts 里的 runtime-root choke-point 测试,以及 acpAgent.ts:9200sessionTurnIndex handler 中第二个直接调用 runWithAcpRuntimeOutputDir 的位置。你所链接的 job(101103789058,Test (ubuntu-latest, Node 22.x))也印证了这一点——经 retry x2 共失败三次,报告 4595 与 9200 两处 mention,而期望只有一处,统计为 Tests 1 failed | 28494 passed | 90 skipped (28585),与描述完全一致。

方向: 对齐。这恢复的是 #10095 为真实 bug 类别引入的约束——handler 手工组合 runtime-root 路由,经由进程级 this.settings 缓存钉住了另一个 workspace 的根目录。陈旧基线的说法站得住:#10751#10988 落地该约束之前新增了 turn-index reader,两者单独运行都通过,合并后并发新增的 reader 留在了 helper 之外。产品形态没有任何变化。CHANGELOG:无直接对应条目,但该区域相关——这是 ACP serve 内部接线,不是面向用户的行为问题。

规模: Stage 0 不适用——packages/cli/src/acp-integration/ 不属于受保护的核心路径,且改动限于单个 package。生产代码 9 行(新增 6、删除 3),测试 0 行,生成/schema 0 行,共 1 个文件。

方案: 换我来写也是这样。per-request 这一层是对的——该 handler 服务调用方传入的 cwd,所以应由 runWithPinnedRuntimeBaseDirForRequest 自己解析 settings,而不是让 handler 做这个决定。它也正好落在同文件中 sessionTranscript 与 settled-turn handler 已有的写法上(把操作提为具名局部变量,再交给 helper),因此文件一致性是提高而非降低。没有可砍的部分,也没有夹带无关改动。

风险: Stage 1e 命中 packages/cli/src/acp-integration/acpAgent.ts,所以我按更深的强度做了 review,并且在批准前要求本 PR 自身的 CI 证据,而不是靠推理判断测试结果。决定性检查(Test (ubuntu-latest, Node 22.x))在撰写时仍在运行中——见 Stage 2 评论。

进入代码审查 🔍

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 40ae7cf148756c211e4d5213be4e861c481c0da1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Code review

I formed my own answer before reading the diff: the invariant test demands exactly one direct mention of runWithAcpRuntimeOutputDir in acpAgent.ts, the handler serves a caller-supplied cwd, so the turn-index read has to move onto runWithPinnedRuntimeBaseDirForRequest — the per-request tier, not runWithPinnedRuntimeBaseDir, which is reserved for callers already holding deliberately scoped settings. Loosening the test's expectation would have been the wrong fix; it would delete the invariant #10095 added. The PR does the right thing, and I found no simpler path it missed.

No critical blockers, and no AGENTS.md violations. What I checked:

Behavior equivalence. The helper chain is runWithPinnedRuntimeBaseDirForRequest(cwd, op)loadSettingsCached(cwd)runWithPinnedRuntimeBaseDir(settings, cwd, () => op(settings))runWithAcpRuntimeOutputDir(settings, cwd, operation)Storage.runWithRuntimeBaseDir(settings.merged.advanced?.runtimeOutputDir, cwd, fn). That is the same settings resolution, the same cwd, and the same pin scope the handler was composing by hand. runWithAcpRuntimeOutputDir returns T rather than Promise<T>, so return await means the same thing before and after.

Nothing lost by hoisting the closure. The extracted body never referenced settings — it uses cwd, sessionId, rawSnapshot, rawStart, rawLimit and this.sessions — so moving it out of the scope where settings was declared captures nothing stale. The helper hands the resolved settings to the operation and this one ignores the parameter, which is a valid assignment for a zero-arg function.

Error mapping intact. loadSettingsCached(cwd) is still reached synchronously from inside the try, so a throw there lands in the same catch; keeping return await keeps rejections inside it too. The InvalidSessionTranscriptCursorError / RangeError / snapshot-unavailable / too-large / ENOENT branches are untouched.

The flush stays inside the pin. The rawSnapshot === undefined branch that flushes the active chat recording is part of readTurnIndexPage, so it runs under the pinned runtime root exactly as before. new SessionTranscriptReader(cwd) and the pagination spread are byte-for-byte unchanged.

No collateral. loadSettingsCached keeps seven other call sites, so the import stays live under noUnusedLocals. The three-line call break is required — one line would be 86 chars against an 80-char width. And the shape now matches its two siblings in the same file, readTranscriptPage (acpAgent.ts:9091) and readSettledTurnResult (acpAgent.ts:12152), which is the consistency the choke-point test exists to enforce.

Two non-blocking observations, neither worth a round trip:

  • The doc comment enumerating which handlers must use the per-request form (acpAgent.ts:4578-4588 — "list, delete, rename, transcript page, settled turn status, and the non-live branch of loadUpdates") doesn't name turn index, which now belongs in that set. A two-word addition if you happen to touch the file again.
  • Turn index has no behavioral test asserting that the request's own settings and cwd reach the pin; sessionTranscript does (acpAgent.test.ts:16940, the multi-workspace case). That is precisely why only the source-level AST walk saw this escape — the runWithAcpRuntimeOutputDir mock is a transparent pass-through (async (_settings, _cwd, fn) => fn()), so no behavioral test can tell the two spellings apart. Pre-existing, and behavior is unchanged here, so I'm not asking this PR to fix it.

Test evidence

Unattended CI run, so per the triage rules I did not build or execute anything from this PR — the evidence below is the PR's own CI plus a static re-derivation I did myself against the base commit.

I did not take the described failure on faith. On base e281bd1d, the choke-point test (acpAgent.test.ts:30538-30592) walks the AST for runWithAcpRuntimeOutputDir identifiers, exempts only the canonical un-aliased import specifier, and asserts the collected mentions equal exactly one string matching /^\d+: return runWithAcpRuntimeOutputDir\(settings, cwd, operation\);$/. Base has two mentions — 4595 (the helper's own delegation, matches) and 9200 (the turn-index call, does not match) — so the failure is deterministic, not flaky. This PR deletes the 9200 mention and introduces no new one, leaving exactly the single expected shape. The linked job log corroborates it independently: the assertion output lists 4595 and 9200, retry x2, Tests 1 failed | 28494 passed | 90 skipped (28585).

The decisive check, Test (ubuntu-latest, Node 22.x), was still running when I wrote this, so I'm deferring approval until CI lands rather than predicting it. No sandboxed lane is needed here: the claim is a source-level invariant with the AST test as its oracle, and that test runs in this PR's own CI — there is no behavioral delta for /verify or /tmux to substantiate. Real-scenario tmux testing: N/A on an unattended run, and nothing user-visible changes.

Final CI results for 40ae7cf (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Test (ubuntu-latest, Node 22.x) ❌ failure
web-shell E2E Smoke (ubuntu-latest, Node 22.x) 🚫 cancelled
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
macos-latest / Java 21 ✅ success
OpenTUI no-flicker gate ✅ success
Real daemon E2E / Java 11 ✅ success
Secret scan (TruffleHog) ✅ success
TUI parity snapshots (ink vs opentui) ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

中文说明

代码审查

在读 diff 之前我先给出了自己的答案:约束测试要求 acpAgent.ts 中直接出现 runWithAcpRuntimeOutputDir 的次数恰好为一次,而该 handler 服务的是调用方传入的 cwd,所以 turn-index 读取必须改走 runWithPinnedRuntimeBaseDirForRequest——即 per-request 这一层,而不是 runWithPinnedRuntimeBaseDir(后者留给已持有刻意限定 settings 的调用方)。放宽测试期望是错误的修法,那会删掉 #10095 引入的约束。本 PR 做的是对的事,我也没有找到它遗漏的更简路径。

无阻断性问题,也没有违反 AGENTS.md。核对过的点:

行为等价。 helper 链路为 runWithPinnedRuntimeBaseDirForRequest(cwd, op)loadSettingsCached(cwd)runWithPinnedRuntimeBaseDir(settings, cwd, () => op(settings))runWithAcpRuntimeOutputDir(settings, cwd, operation)Storage.runWithRuntimeBaseDir(settings.merged.advanced?.runtimeOutputDir, cwd, fn)。这与原先 handler 手工组合的 settings 解析、cwd 和 pin 作用域完全一致。runWithAcpRuntimeOutputDir 返回 T 而非 Promise<T>,因此 return await 的语义前后不变。

提出闭包没有丢东西。 被提取的函数体从未引用 settings——它只用 cwdsessionIdrawSnapshotrawStartrawLimitthis.sessions——所以移出原本声明 settings 的作用域不会捕获到陈旧值。helper 会把解析出的 settings 传给 operation,而此处忽略该参数;零参函数赋给该签名是合法的。

错误映射保持。 loadSettingsCached(cwd) 仍在 try 内被同步调用,因此其抛错仍落入同一个 catch;保留 return await 也让 rejection 留在 try 内。InvalidSessionTranscriptCursorError / RangeError / snapshot 不可用 / 超限 / ENOENT 各分支均未改动。

flush 仍在 pin 内。 rawSnapshot === undefined 时 flush 活跃聊天记录的分支属于 readTurnIndexPage,因此和之前一样在被钉住的 runtime root 下执行。new SessionTranscriptReader(cwd) 与分页参数展开逐字未变。

无附带影响。 loadSettingsCached 还有七处调用点,因此在 noUnusedLocals 下该 import 仍然有效。三行的调用换行是必需的——单行为 86 字符,超出 80 字符宽度。改后形态与同文件中两个同类 handler(acpAgent.ts:9091readTranscriptPageacpAgent.ts:12152readSettledTurnResult)一致,而这正是 choke-point 测试要强制的一致性。

两点非阻断观察,都不值得为此多走一轮:

  • 列举哪些 handler 必须使用 per-request 形式的文档注释(acpAgent.ts:4578-4588——“list、delete、rename、transcript page、settled turn status,以及 loadUpdates 的非 live 分支”)没有提到 turn index,而它现在属于该集合。若日后再改这个文件,可顺手加两个词。
  • turn index 没有行为测试断言请求自身的 settings 与 cwd 抵达 pin;sessionTranscript 有(acpAgent.test.ts:16940 的 multi-workspace 用例)。这恰恰解释了为什么只有源码级 AST 遍历发现了这次漏网——runWithAcpRuntimeOutputDir 的 mock 是透明转发(async (_settings, _cwd, fn) => fn()),因此没有任何行为测试能区分两种写法。这是既有缺口,且此处行为未变,所以不要求本 PR 修补。

测试证据

本次为无人值守 CI 运行,按 triage 规则我没有构建或执行本 PR 的任何代码——下面的证据来自 PR 自身的 CI,以及我在基线提交上独立做的静态推导。

我没有直接采信所描述的失败。在基线 e281bd1d 上,choke-point 测试(acpAgent.test.ts:30538-30592)会遍历 AST 查找 runWithAcpRuntimeOutputDir 标识符,只豁免未加别名的规范 import specifier,并断言收集到的 mention 恰好等于一条匹配 /^\d+: return runWithAcpRuntimeOutputDir\(settings, cwd, operation\);$/ 的字符串。基线有两条 mention——4595(helper 自身的委托,匹配)与 9200(turn-index 调用,不匹配)——因此失败是确定性的,不是 flaky。本 PR 删掉了 9200 这条且未新增任何一条,恰好留下期望的唯一形态。所链接的 job 日志也独立印证:断言输出列出 45959200retry x2Tests 1 failed | 28494 passed | 90 skipped (28585)

决定性检查 Test (ubuntu-latest, Node 22.x) 在撰写时仍在运行,因此我选择推迟批准、等 CI 落地,而不是预测结果。此处不需要沙箱验证通道:该主张是源码级约束,其判定标准就是 AST 测试,而该测试在本 PR 自身的 CI 中运行——不存在需要 /verify/tmux 去证实的行为差异。真实场景 tmux 测试:无人值守运行下为 N/A,且没有用户可见变化。

上方表格为 CI 检查的真实名称与结论(英文部分),此处不再重复。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 40ae7cf148756c211e4d5213be4e861c481c0da1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — the fix is exactly the right one and I confirmed the failure independently; my only reservation is a stale doc-comment enumeration, not the code.

The part that actually mattered here was not taking the framing on faith. "A source-level invariant test fails" is an easy claim to wave through, and this failure is invisible to every behavioral test in the file — the runWithAcpRuntimeOutputDir mock is a transparent pass-through, so the direct call and the routed call behave identically and nothing but an AST walk can tell them apart. So I went and read the base commit instead: the test expects exactly one mention, base e281bd1d has two (4595 the helper's own delegation, 9200 the turn-index reader), which means a required check is red on main right now. That reframes the PR — it is not an improvement to consider, it is the thing unblocking the queue, and it should land quickly.

The approach matches what I would have written, and it lands on the shape two sibling handlers in the same file already use, so acpAgent.ts ends up more consistent than it started. Nine lines, correct tier (per-request, because the handler serves a caller-supplied cwd), nothing unrelated riding along, error mapping and the in-scope recording flush both preserved. In six months this reads fine: a handler composing runtime-root routing by hand is precisely the bug class #10095 removed, and this puts the last straggler back on the shared path.

On volume — the author has roughly twenty open PRs at the moment. I evaluated this one on its own evidence rather than on the strength of the description, and the count does not change the answer. It does make me glad this one is small enough to verify completely.

The single thing I would add, and would not block on: the doc comment enumerating which handlers must take the per-request form still doesn't name turn index. Two words, next time someone is in the file.

Approval is deferred, not withheld. Test (ubuntu-latest, Node 22.x) was still running when I finished, and approving now would attest to a result that does not exist yet — so I've left the deferred-approval marker pinned to this commit below. If CI lands green on 40ae7cf148756c211e4d5213be4e861c481c0da1, the approval follows automatically; if anything lands red or the head moves, it will not.

中文说明

Confidence: 4/5 —— 修法完全正确,且我独立确认了失败本身;唯一的保留意见是一处文档注释的列举已过时,与代码无关。

这里真正要紧的是不轻信 PR 的表述。“某个源码级约束测试失败”是一个很容易被放行的说法,而这次失败对文件里所有行为测试都是不可见的——runWithAcpRuntimeOutputDir 的 mock 是透明转发,直接调用与经 helper 调用行为完全一致,除 AST 遍历外无法区分。所以我改为直接读基线提交:测试期望恰好一条 mention,而基线 e281bd1d 有两条(4595 是 helper 自身的委托,9200 是 turn-index reader),这意味着此刻 main 上有一个必需检查是红的。这改变了本 PR 的性质——它不是一个待斟酌的改进,而是疏通队列的那一把钥匙,应当尽快合入。

方案与我会写的一致,并且落在同文件中两个同类 handler 已有的形态上,因此 acpAgent.ts 改后比起改前更一致。九行,层级正确(per-request,因为该 handler 服务调用方传入的 cwd),没有夹带无关改动,错误映射与作用域内的记录 flush 均保留。半年后再看也没问题:handler 手工组合 runtime-root 路由正是 #10095 消除的那类 bug,本 PR 把最后一个漏网者放回了共享路径。

关于数量——作者目前约有二十个开放 PR。我是依据本 PR 自身的证据、而非描述的说服力来评估的,数量不改变结论。但也确实让我庆幸这一个足够小,可以完整验证。

我唯一会补、且不会因此阻断的一点:列举哪些 handler 必须使用 per-request 形式的文档注释仍未提到 turn index。两个词的事,等下次有人改到这个文件时顺手加上。

批准是推迟,不是否决。我完成审查时 Test (ubuntu-latest, Node 22.x) 仍在运行,此刻批准等于为一个尚不存在的结果背书——因此我在下方留下了绑定该提交的延迟批准标记。若 CI 在 40ae7cf148756c211e4d5213be4e861c481c0da1 上全绿,批准会自动跟上;若有检查变红或 head 发生移动,则不会。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 40ae7cf148756c211e4d5213be4e861c481c0da1 · re-run with @qwen-code /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.

Partially reviewed — gaps disclosed. Suggestions are inline.

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

中文说明

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

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

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

Comment on lines +9218 to +9221
return await this.runWithPinnedRuntimeBaseDirForRequest(
cwd,
readTurnIndexPage,
);

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] R1-1: The turn-index handler is the only one of the seven handlers routed through runWithPinnedRuntimeBaseDirForRequest that has no behavioral routing test pinning that the request's settings and cwd reach the pin. The six sibling handlers each have one (sessionTurnStatus, sessionTranscript, deleteSession, renameSession, listSessions, non-live loadUpdates — acpAgent.test.ts:15272, 16905, 21214, 21252, 21289, 21323). The existing turn-index tests pass no caller-supplied cwd and assert nothing about loadSettings/runWithAcpRuntimeOutputDir, and the AST choke-point test only forbids naming runWithAcpRuntimeOutputDir directly — its own comment ("pinned behaviorally … by the routing tests above") presumes coverage this handler does not have.

If a future edit re-pins this handler from the process-wide cache (e.g. this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage)), the AST choke-point test stays green and no existing turn-index test detects it, so in a multi-workspace daemon a turn-index request naming another workspace's cwd scans the boot workspace's runtime root and returns the wrong transcript page or a spurious ENOENT (-32002/-32010) — the exact #10095 bug class this choke point exists to prevent.

Witness:

Probe (scratch tree) at 40ae7cf:
MUTANT this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage) + full suite:
  Tests 621 passed (621)        ← nothing detects the regression
MUTANT + suggested routing test:
  × expected "spy" to be called with arguments: [ '/tmp/workspace-a' ]
INTACT + suggested routing test:
  Tests 1 passed | 621 skipped

Suggested fix: add a routing test beside the sibling ones, mirroring resolves qwen/status/session/transcript settings per request, not from the this.settings cache (acpAgent.test.ts:16905): boot the agent for one workspace, mock loadSettings to return a distinguishable per-request settings object, call extMethod(SERVE_STATUS_EXT_METHODS.sessionTurnIndex, { cwd: '/tmp/workspace-a', sessionId }), and assert loadSettings was called with '/tmp/workspace-a' and runWithAcpRuntimeOutputDir received (perRequestSettings, '/tmp/workspace-a').

Note the fix must respect that this route derives cwd as const cwd = requestedCwd || process.cwd() (acpAgent.ts:8500), so the test must pass an explicit cwd in the request params, and the assertions must target the module-mocked runWithAcpRuntimeOutputDir (acpAgent.test.ts:878) because the helper delegates to it (acpAgent.ts:4595).

Acceptance check: the new test must go red if line 9218 is mutated to pin from this.settings (this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage)) — please run that mutation once to confirm.

中文说明

turn-index 处理器是七个经由 runWithPinnedRuntimeBaseDirForRequest 路由的处理器中唯一没有行为级路由测试的——该测试用于确保请求的 settings 与 cwd 真正传入 pin。其余六个同级处理器各有一个(sessionTurnStatussessionTranscriptdeleteSessionrenameSessionlistSessions、非 live 分支的 loadUpdates,见 acpAgent.test.ts:15272、16905、21214、21252、21289、21323)。现有的 turn-index 测试不传调用方指定的 cwd,也不对 loadSettings/runWithAcpRuntimeOutputDir 做任何断言,而 AST 扼制点测试只禁止直接写出 runWithAcpRuntimeOutputDir——它自己的注释("pinned behaviorally … by the routing tests above")预设了该处理器并不具备的覆盖。

如果未来某次修改把这个处理器重新固定到进程级缓存(例如 this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage)),AST 扼制点测试仍会通过,现有 turn-index 测试也不会发现;在多工作区 daemon 中,一个指定了其他工作区 cwd 的 turn-index 请求会扫描启动工作区的 runtime root,返回错误的 transcript 页面或误报 ENOENT(-32002/-32010)——正是该扼制点要防的 #10095 一类 bug。

验证证据(临时树探针,提交 40ae7cf):将处理器突变为从 this.settings 固定后,整套测试仍然全部通过(621 passed),没有任何测试发现该回归;加上建议新增的路由测试后,突变版本失败(期望 "spy" 以 [ '/tmp/workspace-a' ] 被调用),完整代码通过。

建议修复:在同级测试旁新增一个路由测试,仿照 resolves qwen/status/session/transcript settings per request, not from the this.settings cache(acpAgent.test.ts:16905):以一个工作区启动 agent,mock loadSettings 返回可区分的按请求 settings,调用 extMethod(SERVE_STATUS_EXT_METHODS.sessionTurnIndex, { cwd: '/tmp/workspace-a', sessionId }),并断言 loadSettings'/tmp/workspace-a' 被调用、runWithAcpRuntimeOutputDir 收到 (perRequestSettings, '/tmp/workspace-a')

注意:该路由以 const cwd = requestedCwd || process.cwd()(acpAgent.ts:8500)派生 cwd,因此测试必须在请求参数中显式传入 cwd;断言必须针对模块级 mock 的 runWithAcpRuntimeOutputDir(acpAgent.test.ts:878),因为 helper 会委托给它(acpAgent.ts:4595)。

验收方式:把第 9218 行突变为从 this.settings 固定(this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage))时,新测试必须变红——请跑一次该突变以确认。

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

@qwen-code-dev-bot qwen-code-dev-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 at head 40ae7cf1.

  • The change is exactly what main needs: it routes the turn-index reader through runWithPinnedRuntimeBaseDirForRequest — the same helper the other six caller-supplied-cwd handlers use, whose body performs the identical loadSettingsCached(cwd) + delegation the inline code did — closing the stale-base violation that has been failing #10988's runtime-root guard on main's Test lane since the #10751 merge (the failure I flagged on #11033, #11025 and #11026). Wrapping the callback body is the established sibling pattern (the transcript reader two scopes away reads identically), and the guard's own AST walk now sees only the permitted delegation at :4595.
  • The one open thread is ci-bot's Suggestion that the turn-index handler lacks a behavioral routing test of its own; six sibling handlers ride the same helper with the source-level invariant as their pin, and this is S-grade under the repo's own rule.
  • CI facts, with the failure attributed by execution: 23 checks pass; the lone Test (ubuntu) red on this head is NOT the guard and NOT this PR — I reproduced it locally at this tree and it is no-ak-integration-ci.test.js expecting the no-AK script list without ./cli/_prompt-latency-policy.test.ts, a merge-window skew between this PR's base and #11004's package.json/test pair that passes on current main (verified: 12 passed on the tip). This PR touches neither file; a base sync/rebase clears it. The web-shell E2E Smoke red is the recurring 20-minute pool cap cancellation. Per the channel convention the call is on the review itself.

@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.

No blocking findings.
Approval blockers: none.

Scope: acpAgent.ts diff only (6 additions, 3 deletions, 1 file). The change is a rebase of 40ae7cf1 onto current main with no semantic delta.

What I checked:

  • Behavioral equivalence: runWithPinnedRuntimeBaseDirForRequest(cwd, op) internally calls loadSettingsCached(cwd) then runWithAcpRuntimeOutputDir(settings, cwd, () => op(settings)) — identical to the removed inline code. The operation body captures this.sessions, sessionId, rawSnapshot, rawCursor, rawLimit from the outer closure; it ignores the settings argument the helper passes, which is correct because the original also did not use settings inside the operation.
  • Error handling: catch block is identical to the base — error types and request codes are unchanged.
  • Choke-point test: acpAgent.test.ts:30537 walks the AST and expects exactly one non-import mention of runWithAcpRuntimeOutputDir: 4595: return runWithAcpRuntimeOutputDir(settings, cwd, operation). After this PR the turn-index handler no longer names it directly, so the test passes.

Confirmed suggestion (ci-bot R1-1): The turn-index handler is the only one of the seven handlers routed through runWithPinnedRuntimeBaseDirForRequest that has no behavioral routing test verifying the request's cwd and settings reach the pin. The six siblings have one each (acpAgent.test.ts:15272, 16905, 21214, 21252, 21289, 21323). The choke-point test's own comment says "pinned behaviorally … by the routing tests above" — that presupposition does not hold for this handler. A mutation probe (from ci-bot's report) confirmed: replacing line 9218 with this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage) leaves all 621 tests green. The suggested fix is a routing test mirroring the sibling at acpAgent.test.ts:16905. Non-blocking — the current routing is correct; the gap is in regression-prevention coverage.

Not covered: No working tree available for a local build or mutation run; rung 3 not applicable (no environment-dependent change).

Reviewed with AI assistance.

)) as unknown as Record<string, unknown>;
});
};
return await this.runWithPinnedRuntimeBaseDirForRequest(

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.

[Confirmed — Suggestion] R1-1: This handler is the only one of the seven runWithPinnedRuntimeBaseDirForRequest callers without a behavioral routing test verifying that the request's cwd and settings reach the pin. Six sibling handlers each have one (acpAgent.test.ts:15272, 16905, 21214, 21252, 21289, 21323). The choke-point test's own comment — "pinned behaviorally … by the routing tests above" — presupposes this coverage, but this handler has none.

Mutation witness (from ci-bot's probe): mutating this call to this.runWithPinnedRuntimeBaseDir(this.settings, cwd, readTurnIndexPage) leaves all 621 tests green, so the #10095 regression class can recur here undetected. The suggested fix (per ci-bot) is a routing test mirroring resolves qwen/status/session/transcript settings per request, not from the this.settings cache (acpAgent.test.ts:16905): boot the agent for one workspace, mock loadSettings to return a distinguishable per-request settings object, call extMethod(SERVE_STATUS_EXT_METHODS.sessionTurnIndex, { cwd: '\/tmp\/workspace-a', sessionId }), and assert loadSettings was called with '\/tmp\/workspace-a' and runWithAcpRuntimeOutputDir received (perRequestSettings, '\/tmp\/workspace-a'). The test must go red when mutated to pin from this.settings.

Non-blocking — current routing is correct.

@yiliang114
yiliang114 added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit eaef97e Sep 5, 2026
57 of 59 checks passed
shenyankm pushed a commit to shenyankm/qwen-code that referenced this pull request Sep 5, 2026
qwen-code-dev-bot pushed a commit that referenced this pull request Sep 5, 2026
qwen-code-dev-bot added a commit that referenced this pull request Sep 5, 2026
The shared parser cast three capability members through unchecked while
every consumer reads them strictly, so a mistyped value in an unvalidated
`modelProviders` settings entry silently changed the declaration instead
of failing it. A truthy non-boolean `toggleOnly` collapsed a declared
ladder to toggle-only: the pickers refused every tier while `disableField`
still reshaped the wire. `efforts: []` passed `[].every()` and emitted a
zero-tier capability that suppressed the manifest fallback and stripped
the effort from the request. A non-`false` `canDisable` degenerated to
absent, so a route declaring that disabling is forbidden still shipped the
disable shape that declaration exists to prevent. All three are now
rejected rather than coerced, along with a repeated tier, which duplicated
picker rows, ACP options and React keys. Well-formed input keeps the
identity contract.

Both effort pickers forced the cursor onto the first tier when the stored
global `model.reasoningEffort` is not one the active model exposes, and
confirming without moving persisted that tier over the stored value -- the
rewrite measured live as F2. A forced cursor now cancels in the ink dialog
and closes without writing in the OpenTUI one; navigating first still
selects, and the unset case is unchanged. The OpenTUI footer discloses the
unset case too, matching ink.

Four output-style dialog tests drove keys before the effect deriving the
cursor from the async catalog had run, so that derivation could overwrite
the navigation and pick the wrong row; they now wait for it.

Every new guard condition has a mutation witness: removing the toggleOnly,
canDisable, empty-efforts or uniqueness check, the ink cursor-moved or
stored-tier condition, or the OpenTUI forced-cursor condition each reddens
the test that pins it, and acceptance of a well-formed `canDisable: false`
or `toggleOnly: false` is pinned as well.

Not changed here: the pipeline still drops a carried-over tier the
capability does not list instead of clamping it onto the ladder. The
maintainer's real-stack A/B records refusing an unsupported tier as
correct and files the carried-over case as a picker problem, and the
author's triage answer names the drop as the requested migration contract,
so the clamp stays a maintainer call and its thread stays open. The
turn-index per-request settings test is deferred: this round's merge of
main absorbed the identical handler fix (#11036), leaving only the roster
docstring here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants