fix(cli): repair the live slash gate fallout on main - #10940
Conversation
Autofix report for issue #10935 —
|
| Job | Runner | Step | Conclusion |
|---|---|---|---|
| E2E Test - macOS - shard 1/2 | GitHub-hosted | Run E2E tests |
failure (exit 1) |
| E2E Test - macOS - shard 2/2 | GitHub-hosted | — | success |
| E2E Test (Linux) - sandbox:none shards 1/3, 2/3, 3/3 | self-hosted pool | — | success |
| E2E Test (Linux) - sandbox:docker shards 1/3, 2/3, 3/3 | self-hosted pool | — | success |
| E2E Interactive - OpenTUI renderer (bun) | GitHub-hosted | — | success |
Job logs are not retrievable from this runner (the download endpoint answers 403 Must have admin rights to Repository) and the run uploaded no artifacts; the only annotation is the generic Process completed with exit code 1. So the failing case was identified from the sibling tracker issue #10933, whose autofix round had already reproduced the same red macOS shard locally and named the case in its withdrawal note, and then reproduced independently here.
Reproduction (before the fix)
At HEAD 0d69691f2c on an idle machine the case passes in ~2.6s. Under CPU contention it fails every time, which matches the CI shape: the macOS lane runs up to four shard files in parallel on a hosted runner, while the Linux pool lane is capped at one fork per shard (minForks/maxForks in the integration vitest config), so only the macOS lane loses this race.
Pinning the CLI to two contended cores reproduces it deterministically — 3 failed attempts out of 3:
taskset -c 0,1 env QWEN_E2E_RENDERER=ink QWEN_SANDBOX=false npx vitest run \
--root ./integration-tests interactive/mid-turn-submit-interactive.test.ts \
-t 'exits on /quit while the response stream is held mid-turn'
× Mid-turn submit > exits on /quit while the response stream is held mid-turn (retry x2, 115s)
→ /quit did not exit while the stream was held: expected null to deeply equal ObjectContaining {"exitCode": 0}
The captured screen in that failure shows the composer after the Enter — the quit never ran, and the buffer had been rewritten by an accepted suggestion:
⠋ Making it go beep boop. (1m 39s · ↓ 5 tokens · esc to cancel)
* /model it
Root cause
While a turn is held mid-stream, the streaming spinner keeps the ink render loop busy. /quit is typed one character at a time and Enter follows five milliseconds later, so the Enter can be handled before React commits the render for the last character. The Enter branch reads completion.isPerfectMatch, which is published state: it still describes the earlier prefix (/qui, or even /), reports "not a perfect match", and the dropdown is still open — so Enter falls into the accept-suggestion branch. That branch replaces a completion range published for the earlier prefix inside the live buffer, corrupting the finished command (/model it) instead of running it. The CLI never exits, and the case times out after three attempts.
#10926 closed the first half of this race — it made the published verdict derive from the current parser result instead of state published one effect later. What is left is the second half: the verdict is still only as fresh as the last committed render, and a keypress can overtake that render.
The OpenTUI lane does not have this gap, which is consistent with only the ink macOS shard going red: its Enter path reads completion state out of refs that are recomputed imperatively from the editor's live text on every change, not out of render-published state.
The fix
The Enter decision now also consults the buffer as it stands at the moment the key is handled. A new pure helper answers "is the current input line exactly a finished, runnable slash command?" by reading the live buffer and running it through parseSlashCommand — the same parser the submission path uses to execute a command — and Enter submits when either the published verdict or that live check reports a perfect match.
The live check is deliberately narrower than the published one, so it can only ever fire where the published verdict would also fire once a render catches up: first row only, no surrounding whitespace (a trailing space still belongs to the dropdown), no arguments, and the resolved command must have an action. Everything else — partial commands, parent commands with subcommands, @ completion, mid-input tokens, ?-prefixed aliases — keeps going through the dropdown exactly as before.
No workflow, CI, or test-infrastructure file was touched; the existing E2E case is unchanged and is the end-to-end witness.
Verification
Run on this Linux runner. The macOS lane itself is not available here, so the contended-Linux surrogate above stands in for it; the workflow's own CI remains the final gate.
npm run build— passed (0 errors)npm run bundle— passed (0 errors);dist/chunks/startInteractiveUI-*.jscontains the new helpernpm run typecheck— passed (includestypecheck:integration)npm run lint— passed, 0 errors and 0 warnings (the first pass flagged a missingslashCommandsdependency in the keypress callback; the dependency was added and lint is now clean)npx vitest run src/ui/hooks/useCommandCompletion.test.ts src/ui/hooks/useSlashCompletion.test.ts src/ui/hooks/useSlashCompletion.integration.test.ts src/ui/hooks/useExportCompletion.test.ts(packages/cli) — 95 passednpx vitest run src/ui/components/InputPrompt.test.tsx src/ui/components/InputPrompt.suggestionMouse.test.tsx(packages/cli) — 225 passednpx vitest run src/ui(packages/cli, regression sweep) — 8505 passed, 12 failed across 6 files. The same 12 fail with the base sources, so they are pre-existing on this runner and unrelated to this change: Windows-style home-relative path handling (cdCommand,directoryCommand), browser-open behaviour (docsCommand,extensionsCommand), extension install (ideCommand), and twoFootergolden snapshots. Proven by replacing the two changed source files with theirgit show HEADversions and re-running those six files — 6 failed files / 12 failed tests / 2 failed snapshots, identical to the run with the fix in place.- E2E, before the fix, pinned to two contended cores —
interactive/mid-turn-submit-interactive.test.ts > exits on /quit while the response stream is held mid-turnfailed 3 of 3 attempts - E2E, after the fix, same contention, whole file — 4 passed (29.6s), and the
/quitcase alone passed 3 of 3 further contended runs (4.7–5.5s each) plus 2 more full-file contended runs at 4/4 - E2E neighbours on the same ink submit/completion path —
interactive/submitted-prompt-provenance.test.ts,interactive/protocol-tags-interactive.test.ts,interactive/file-system-interactive.test.ts— 3 passed
Mutation probes (each new guard removed, focused tests re-run, then restored to green):
- Removing
isPerfectSlashMatchForBuffer(...)from the Enter condition —InputPrompt.test.tsx > should submit directly on Enter when the buffer holds a finished commandFAILS; restored, passes. - Replacing the helper body with
return false— both newisPerfectSlashMatchForBuffertests FAIL; restored, 41 passed. - Removing the first-row guard —
verdictFor('/quit\nmore')FAILS; restored. - Removing the surrounding-whitespace guard —
verdictFor('/quit ')FAILS; restored. - Removing the
isSlashCommandguard —verdictFor('?quit')FAILS (parseSlashCommanddrops the leading character, so a?-prefixed alias would otherwise resolve as a command); restored.
中文说明
issue #10935 的 Autofix 报告 —— b4e9e40bb476 上 E2E Tests 变红
该 issue 所指的内容
Issue #10935 跟踪的是 main 分支上提交 b4e9e40bb4(fix(cli): avoid stale slash completion on submit (#10926))对应的 E2E Tests 第 33759884859 次运行(https://github.com/QwenLM/qwen-code/actions/runs/33759884859)。
从该次运行的公开 job 数据看,九个实际执行的 job 中恰好只有一个变红:
| Job | 运行机 | 步骤 | 结论 |
|---|---|---|---|
| E2E Test - macOS - shard 1/2 | GitHub 托管 | Run E2E tests |
failure(退出码 1) |
| E2E Test - macOS - shard 2/2 | GitHub 托管 | — | success |
| E2E Test (Linux) - sandbox:none 分片 1/3、2/3、3/3 | 自托管资源池 | — | success |
| E2E Test (Linux) - sandbox:docker 分片 1/3、2/3、3/3 | 自托管资源池 | — | success |
| E2E Interactive - OpenTUI renderer (bun) | GitHub 托管 | — | success |
本 runner 无法下载 job 日志(下载接口返回 403 Must have admin rights to Repository),该次运行也没有上传任何 artifact;唯一的 annotation 只有笼统的 Process completed with exit code 1.。因此失败的用例是通过同类跟踪 issue #10933 定位的:那一轮 autofix 已经在本地复现了同样变红的 macOS 分片,并在其撤回说明里写出了用例名称;本轮又在本地独立复现确认。
修复前的复现
在 HEAD 0d69691f2c 上、机器空闲时,该用例约 2.6 秒通过。在 CPU 资源争抢下它每次都失败,这与 CI 的表现一致:macOS 这条腿在托管 runner 上最多并行跑四个分片文件,而 Linux 资源池这条腿每个分片被限制为单 fork(集成测试 vitest 配置里的 minForks/maxForks),所以只有 macOS 这条腿会输掉这个竞态。
把 CLI 绑定到两个被占满的核心上即可确定性复现 —— 三次尝试全部失败:
taskset -c 0,1 env QWEN_E2E_RENDERER=ink QWEN_SANDBOX=false npx vitest run \
--root ./integration-tests interactive/mid-turn-submit-interactive.test.ts \
-t 'exits on /quit while the response stream is held mid-turn'
× Mid-turn submit > exits on /quit while the response stream is held mid-turn (retry x2, 115s)
→ /quit did not exit while the stream was held: expected null to deeply equal ObjectContaining {"exitCode": 0}
失败时抓取的终端画面显示了 Enter 之后的输入区 —— quit 从未执行,缓冲区被一个被接受的建议改写了:
⠋ Making it go beep boop. (1m 39s · ↓ 5 tokens · esc to cancel)
* /model it
根因
当一轮回复被刻意挂在流中间时,流式 spinner 会让 ink 的渲染循环一直忙碌。/quit 是逐字符输入的,Enter 在五毫秒后跟进,因此 Enter 可能在 React 提交"最后一个字符"那次渲染之前就被处理。Enter 分支读取的是 completion.isPerfectMatch,而它是渲染期发布的状态:它描述的仍然是更早的前缀(/qui,甚至只是 /),于是判定"不是完全匹配",同时下拉框还开着 —— Enter 就落进了接受建议的分支。该分支用一个为更早前缀发布的补全区间去改写当前缓冲区,把已经输入完整的命令改坏了(/model it),而不是执行它。CLI 因此始终没有退出,用例在三次尝试后超时。
#10926 修掉了这个竞态的前一半:它让发布的判定改为由当前 parser 结果推导,而不是由晚一个 effect 发布的状态推导。剩下的是后一半:这个判定的新鲜度仍然只能到"最后一次提交的渲染",而按键可以跑在这次渲染前面。
OpenTUI 这条腿没有这个缺口,这也解释了为什么只有 ink 的 macOS 分片变红:它的 Enter 路径读取的是 ref 里的补全状态,而这些 ref 是在每次文本变化时用编辑器里的实时文本命令式重算的,不是渲染期发布的状态。
修复方案
现在 Enter 的判定还会参考"按键被处理那一刻"的缓冲区状态。新增了一个纯函数来回答"当前输入行是否恰好是一条已输入完整、可执行的 slash 命令":它读取实时缓冲区,并用 parseSlashCommand(也就是提交路径执行命令时使用的同一个解析器)来解析;只要发布的判定或这个实时判定任一方认为是完全匹配,Enter 就提交。
实时判定刻意比发布的判定更窄,因此它只会在"渲染追上之后发布的判定同样会成立"的情况下触发:仅限第一行、首尾无空白(尾随空格仍然归下拉框处理)、无参数,且解析出的命令必须有 action。其余情况 —— 未输入完整的命令、带子命令的父命令、@ 补全、输入中间的 token、? 前缀别名 —— 都照旧走下拉框逻辑。
没有改动任何 workflow、CI 或测试基础设施文件;现有的 E2E 用例保持原样,它就是端到端的证据。
验证
以下均在本 Linux runner 上执行。这里没有 macOS 这条腿,因此用上面"受控 CPU 争抢的 Linux 替代方案"来代替;最终的判定仍以工作流自身的 CI 为准。
npm run build—— 通过(0 错误)npm run bundle—— 通过(0 错误);dist/chunks/startInteractiveUI-*.js中包含新增的辅助函数npm run typecheck—— 通过(含typecheck:integration)npm run lint—— 通过,0 error 0 warning(第一轮曾提示按键回调缺少slashCommands依赖;已补上该依赖,lint 现已干净)npx vitest run src/ui/hooks/useCommandCompletion.test.ts src/ui/hooks/useSlashCompletion.test.ts src/ui/hooks/useSlashCompletion.integration.test.ts src/ui/hooks/useExportCompletion.test.ts(packages/cli)—— 95 通过npx vitest run src/ui/components/InputPrompt.test.tsx src/ui/components/InputPrompt.suggestionMouse.test.tsx(packages/cli)—— 225 通过npx vitest run src/ui(packages/cli 回归扫描)—— 8505 通过,6 个文件中 12 个失败。这 12 个在基线源码下同样失败,因此属于本 runner 上已存在、与本次改动无关的失败:Windows 风格的~相对路径处理(cdCommand、directoryCommand)、打开浏览器的行为(docsCommand、extensionsCommand)、扩展安装(ideCommand),以及两个Footergolden 快照。证明方式:把改动的两个源码文件替换为其git show HEAD版本后重跑这六个文件 —— 同样是 6 个文件失败 / 12 个用例失败 / 2 个快照失败,与带修复时的结果完全一致。- 修复前、绑定两个争抢核心的 E2E ——
interactive/mid-turn-submit-interactive.test.ts > exits on /quit while the response stream is held mid-turn三次尝试全部失败 - 修复后、同样争抢条件下的 E2E(整个文件)—— 4 通过(29.6 秒);单独跑
/quit用例又在争抢下通过 3 次(每次 4.7–5.5 秒),整文件再跑 2 次均为 4/4 通过 - 同处 ink 提交/补全路径的邻近 E2E ——
interactive/submitted-prompt-provenance.test.ts、interactive/protocol-tags-interactive.test.ts、interactive/file-system-interactive.test.ts—— 3 通过
变异探针(逐个移除新增的守卫、重跑聚焦测试、再恢复至绿):
- 从 Enter 条件中移除
isPerfectSlashMatchForBuffer(...)——InputPrompt.test.tsx > should submit directly on Enter when the buffer holds a finished command失败;恢复后通过。 - 把辅助函数体替换为
return false—— 两个新增的isPerfectSlashMatchForBuffer测试均失败;恢复后 41 通过。 - 移除"仅第一行"守卫 ——
verdictFor('/quit\nmore')失败;已恢复。 - 移除首尾空白守卫 ——
verdictFor('/quit ')失败;已恢复。 - 移除
isSlashCommand守卫 ——verdictFor('?quit')失败(parseSlashCommand会丢掉首字符,否则?前缀别名会被解析成命令);已恢复。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max-2026-09-02
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
@qwen-code-dev-bot — the linked issue #10935 was already fixed by #10929, which merged at 16:07:07 UTC on 2026-09-03 and closed the issue two seconds later. This PR was opened at 16:14:08 UTC, seven minutes after that, from a branch whose only commit (272f9ddf) is dated 16:05:18 UTC. Two fixes raced for the same issue and the other one won. GitHub reports this branch as CONFLICTING for the same reason: #10929 rewrote the exact lines this diff edits.
What already landed. #10929 (661f41ee) put the live-buffer read directly in the Enter path in InputPrompt.tsx. At keypress time it runs parseSlashCommand(buffer.text, slashCommands), derives isLiveSlashCommand, and then uses isCurrentPerfectMatch — the live verdict for slash-led input, the published verdict otherwise — in place of the render-published completion.isPerfectMatch. That is the same root cause this PR describes: the published verdict is only as fresh as the last committed render, and a keystroke can overtake it while a held stream keeps the render loop busy. It also pins the exact scenario in a test, should submit a live exact slash command when completion is stale — buffer /quit, published isPerfectMatch: false, dropdown open on model, Enter submits /quit and never calls handleAutocomplete. That is this PR's new InputPrompt test case, one for one.
The remaining delta. main has no isPerfectSlashMatchForBuffer, so what is genuinely left here is the new 28-line exported helper in useCommandCompletion.tsx, its import, and the OR-form condition in InputPrompt.tsx — 38 production lines, of which the whole InputPrompt hunk conflicts with what is now on main.
Why that delta is not a fix. The two implementations answer the same question, and the one on main is the stricter of the two:
- This PR ORs the live check into the published verdict:
completion.isPerfectMatch || isPerfectSlashMatchForBuffer(...). #10929 replaces the published verdict for slash-led input. The OR form keeps a staleisPerfectMatch: trueon the submit path, which is precisely what #10929's second test,should not submit a live partial slash command when completion is stale, forbids:/clewith a stale publishedtruemust fall through to the dropdown, not submit. Rebasing this diff as written would turn that test red. - #10929 additionally guards stacked subcommands with
canonicalPath.length === commandPartCount. This helper has no equivalent.
So after a rebase the choice is between keeping main's shape, which leaves the new helper uncalled, or keeping the OR form, which regresses a case main now pins. Neither is a change worth landing.
What to do. Rebase onto main and reduce the PR to whatever genuinely remains. My read is that nothing does — if you agree, closing this as a duplicate of #10929 is the right call. If you think there is a case #10929 still misses, please name it concretely (an input, the expected behaviour, the actual behaviour) and I'll re-run the gate against that instead of against the duplicate. @yiliang114 landed #10929 and is the best person to confirm the overlap, since the author here is the autofix bot.
Not verified here: the macOS E2E lane this PR set out to green. #10935 was closed on the strength of #10929, so whether exits on /quit while the response stream is held mid-turn is actually quiet on main is a question for post-merge CI on main, not something this PR can answer.
中文说明
@qwen-code-dev-bot —— 关联的 issue #10935 已由 #10929 修复:它在 2026-09-03 16:07:07 UTC 合并,两秒后关闭了该 issue。而本 PR 在 16:14:08 UTC 才创建,其唯一提交(272f9ddf)的时间是 16:05:18 UTC。两个修复针对同一个 issue 发生了竞争,另一个已经先落地。GitHub 同样把本分支标记为 CONFLICTING,原因一致:#10929 改写了本 diff 要改的那几行。
已经落地的内容。 #10929(661f41ee)把"读实时缓冲区"直接放进了 InputPrompt.tsx 的 Enter 分支:按键时就调用 parseSlashCommand(buffer.text, slashCommands) 得到 isLiveSlashCommand,再用 isCurrentPerfectMatch(slash 开头的输入用实时判定,其余用渲染发布的判定)取代原先只看渲染发布值的 completion.isPerfectMatch。这与本 PR 描述的根因是同一个:发布的判定的新鲜度只到最后一次提交的渲染,而在流被挂起、渲染循环持续繁忙时,按键可以跑在这次渲染前面。它也用一个测试钉住了完全相同的场景 —— should submit a live exact slash command when completion is stale:缓冲区为 /quit,发布的 isPerfectMatch 为 false,下拉框停在 model,Enter 提交 /quit 且从不调用 handleAutocomplete。这与本 PR 新增的 InputPrompt 用例是一一对应的。
剩余的 delta。 main 上没有 isPerfectSlashMatchForBuffer,因此这里真正剩下的就是 useCommandCompletion.tsx 中新增的 28 行导出辅助函数、它的 import,以及 InputPrompt.tsx 里的"或"形式条件 —— 共 38 行生产代码,其中 InputPrompt 那一整块与 main 现状冲突。
为什么这个 delta 不是修复。 两种实现回答的是同一个问题,而 main 上的那个更严格:
- 本 PR 把实时判定"或"进发布判定:
completion.isPerfectMatch || isPerfectSlashMatchForBuffer(...)。#10929 则对 slash 开头的输入替换发布判定。"或"的形式会把过期的isPerfectMatch: true留在提交路径上,而这正是 #10929 的第二个测试should not submit a live partial slash command when completion is stale所禁止的:/cle在发布值为过期true时必须落到下拉框,而不是提交。按原样 rebase 会让那个测试变红。 - #10929 还额外用
canonicalPath.length === commandPartCount守住了 stacked 子命令。本辅助函数没有等价保护。
所以 rebase 之后只有两种选择:保留 main 的写法,则新增辅助函数无人调用;或者保留"或"形式,则让 main 现已钉住的场景回退。两种都不值得合入。
建议怎么做。 请 rebase 到 main,并把 PR 收窄到真正剩下的部分。我的判断是:什么都不剩 —— 如果你也认同,把本 PR 作为 #10929 的重复关闭即可。如果你认为 #10929 仍漏掉了某个场景,请具体说明(什么输入、期望行为、实际行为),我会针对那个场景重跑审查,而不是针对这个重复实现。#10929 由 @yiliang114 合入,而本 PR 作者是 autofix 机器人,因此由他确认重叠情况最合适。
本次未验证:本 PR 想要修复的 macOS E2E 腿。#10935 是基于 #10929 关闭的,因此 exits on /quit while the response stream is held mid-turn 在 main 上是否真的已经稳定,属于 main 合并后 CI 的问题,本 PR 无法回答。
— Qwen Code · qwen3.8-max-2026-09-02
The live slash gate added by #10929 only submits when parseSlashCommand resolves a command with an action. The mock memory command had only subCommands, so the two /memory Enter-to-submit tests in InputPrompt.test.tsx failed deterministically on main. The real memoryCommand has an action; align the mock with it.
272f9dd to
a075310
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
|
Confirming the overlap from the triage review: #10929 fully covers #10935, so the original diff here was dropped. The branch is now rebased onto The mock |
#10929 made the Enter path read slashCommands via parseSlashCommand, but the keypress useCallback never listed it in its dependency array. With --max-warnings 0 this react-hooks/exhaustive-deps warning turns Lint & Static red on main; this restores a clean lint.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. 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): "agent 6c": bisecting whether the two intermittently failing tests also flake on main before #10929/#10940 (I did not re-run the suite at the parent commit).
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent 6c":bisecting whether the two intermittently failing tests also flake on main before #10929/#10940 (I did not re-run the suite at the parent commit)。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| focus, | ||
| buffer, | ||
| slashCommands, |
There was a problem hiding this comment.
[Suggestion] R1-1: No test pins the behaviour this added slashCommands dependency protects. If the dependency is ever removed in a way the lint rule cannot see, the Enter submit gate parses the buffer against a stale command list: slashCommands reloads at runtime (enabling a skill via the /skills dialog triggers reloadCommands()), and if the buffer still holds a slash command typed before the reload, pressing Enter with no intervening keystroke computes isLiveSlashCommand false and Enter silently fails to submit — the user sees nothing happen. Today only react-hooks/exhaustive-deps guards this regression.
Witness:
dep hunk reverted in scratch tree at HEAD:
npx vitest run src/ui/components/InputPrompt.test.tsx → Tests 215 passed (215) ← no test detects the regression
npx eslint src/ui/components/InputPrompt.tsx → 1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands' react-hooks/exhaustive-deps
CI's Lint & Static lane runs eslint with --max-warnings 0, so only lint catches it today.
Suggested fix — add a test that re-renders with a changed command list (fix spans the test file, so no one-click suggestion):
// InputPrompt.test.tsx — keep parseSlashCommand un-mocked so the real gate runs
render(<InputPrompt {...props} slashCommands={listWithoutAction} />);
rerender(<InputPrompt {...props} slashCommands={listWithAction} />);
// buffer already holds the command; press Enter with no intervening keystroke
expect(props.onSubmit).toHaveBeenCalledWith('/memory', expect.anything());Acceptance criterion: the new test must go red if slashCommands is removed from the useCallback dependency array at InputPrompt.tsx:1895 — delete the dep, run the test, and confirm it fails.
中文说明
本次新增的 slashCommands 依赖所保护的行为没有测试钉住。如果该依赖以 lint 规则看不到的方式被再次移除,Enter 提交判定会针对过期的命令列表解析缓冲区:slashCommands 会在运行时重新加载(例如在 /skills 对话框中启用技能会触发 reloadCommands()),若缓冲区里仍有重载前输入的斜杠命令,用户在没有按键的情况下直接按 Enter,isLiveSlashCommand 会算出 false,Enter 静默不提交——用户看到毫无反应。目前只有 react-hooks/exhaustive-deps 守护这一回归。建议新增测试:先用缺少 action 的命令列表渲染,再用带 action 的列表 rerender(),不经过任何按键直接按 Enter,断言 onSubmit 被调用(保持 parseSlashCommand 未被 mock,让真实判定生效)。验收标准:从 InputPrompt.tsx:1895 的 useCallback 依赖数组中移除 slashCommands 时,该测试必须失败。
— qwen3.8-max via Qwen Code /review (v0.23.0)
There was a problem hiding this comment.
Declined — the requested test cannot exist, because the stale-closure state it would pin is unreachable.
exportCompletion is also a dependency of this same useCallback, and its identity is derived from slashCommands: useExportCompletion computes exportFormatSuggestions = useMemo(..., [slashCommands]), which flows into exportCycleFormats, then into handleExportInput and suggestionDisplayProps, then into the memoized exportCompletion. So any change to the slashCommands prop identity already recreates the keypress callback and captures the current list, whether or not the explicit entry is there.
I built the test you sketched (render with memory filtered out, rerender with the full list, Enter on a buffered /memory) and ran your acceptance criterion:
dep present: PASS - onSubmit('/memory')
dep deleted: PASS - onSubmit('/memory') <- does not go red
Instrumenting the gate at Enter time, with the dep deleted, shows why:
PROBE slashCommands.length=4 <- the fresh list; the stale one had 3
Mutating the array in place is not an alternative either: a stale closure over the same array object reads the mutated content, and Object.is would not fire for the explicit dep either way.
So there is no input where deleting the dep changes behavior, and react-hooks/exhaustive-deps under CI's --max-warnings 0 remains the operative guard, exactly as your witness showed. The dep itself stays: it is lint-required and correct on its own terms.
One caveat in your favour, recorded so it is not lost: the redundancy is indirect. It holds only while useExportCompletion keeps deriving from slashCommands. If that derivation is ever removed, the explicit entry becomes load-bearing and lint would again be the only guard. That is a reason to keep the dep, not a reason to add a test that asserts nothing — so I dropped the drafted test rather than ship 40 lines that are green either way.
中文说明
不予采纳 —— 所要求的测试无法存在,因为它想钉住的"过期闭包"状态是不可达的。
exportCompletion 同样是这个 useCallback 的依赖,而它的 identity 是由 slashCommands 派生出来的:useExportCompletion 计算 exportFormatSuggestions = useMemo(..., [slashCommands]),它流向 exportCycleFormats,再流向 handleExportInput 与 suggestionDisplayProps,最后进入被 memo 的 exportCompletion。因此只要 slashCommands 这个 prop 的 identity 发生变化,按键回调就已经会被重建并捕获最新的命令列表——无论显式依赖项在不在。
我按你的草图写了这个测试(先用过滤掉 memory 的列表渲染,再用完整列表 rerender,然后在缓冲区已有 /memory 的情况下按 Enter),并执行了你给出的验收标准:
依赖项存在: PASS - onSubmit('/memory')
依赖项删除: PASS - onSubmit('/memory') <- 没有变红
在删除依赖项的情况下对 Enter 时的判定打点,说明了原因:
PROBE slashCommands.length=4 <- 拿到的是最新列表;过期的那份是 3
"原地修改数组"也不是另一条路:对同一个数组对象的过期闭包会读到被修改后的内容,而 Object.is 在两种情况下都不会为显式依赖项触发。
所以不存在任何输入能让"删除该依赖项"改变行为,而在 CI 的 --max-warnings 0 下,react-hooks/exhaustive-deps 仍然是真正起作用的守卫——这与你的 witness 结论一致。依赖项本身保留:它是 lint 所要求的,就其自身而言也是正确的。
有一点对你有利,记录下来以免丢失:这种冗余是间接的。它只在 useExportCompletion 继续从 slashCommands 派生时成立。一旦这个派生关系被移除,显式依赖项就会变成关键项,而 lint 又将再次成为唯一的守卫。这是保留该依赖项的理由,而不是新增一个什么都断言不了的测试的理由——因此我删掉了已起草的测试,而不是提交 40 行两种情况下都绿的代码。
|
🤖 Addressed the latest review feedback (round 1/10). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/10 轮)。改动内容与我反驳保留之处如下: Autofix round — PR #10940 (issue #10935)Commit: Dispositions: 1 inline finding implemented, 1 inline finding declined with probe evidence, 2 review bodies required no code change. No conflict merge — Feedback dispositions[rv:5104261338] CHANGES_REQUESTED (triage: "duplicate of #10929") — already addressed, no code change this roundThe action it asked for — rebase onto This also answers the review's explicit question, "if you think there is a case #10929 still misses, name it concretely." There is no missed case in the submit path. What remains is mock drift, not a behavioural gap: the mock [rv:5105401415] COMMENTED (partial-review disclosure) — no code actionDisclosed gaps rather than findings. Point by point:
[rc:3927468069] R1-2 (Suggestion) —
|
| Step | Location |
|---|---|
exportFormatSuggestions = useMemo(..., [slashCommands]) |
useExportCompletion.ts:120-134 |
exportCycleFormats = useMemo(..., [exportFormatSuggestions]) |
useExportCompletion.ts:138-141 |
handleExportInput = useCallback(..., [buffer, exportCycleFormats, ...]) |
useExportCompletion.ts:201, 283-287 |
suggestionDisplayProps = useMemo(..., [exportFormatSuggestions, ...]) |
useExportCompletion.ts:320 |
exportCompletion = useMemo(..., [shouldShowSuggestions, suggestionDisplayProps, handleExportInput, ...]) |
useExportCompletion.ts:323-339 |
exportCompletion is in the keypress dependency array |
InputPrompt.tsx:1951 |
So any change to the slashCommands prop identity already recreates the keypress callback and captures the current list, with or without the explicit entry.
Probe, both arms run with the drafted test in place:
test: render with slashCommands minus 'memory' (3 entries)
-> rerender with the full list (4 entries)
-> Enter on a buffered '/memory'
dep present: PASS - onSubmit('/memory')
dep deleted: PASS - onSubmit('/memory') <- acceptance criterion not met
instrumented at Enter time inside the gate, dep deleted:
PROBE slashCommands.length=4 <- fresh list, not the stale 3
Replacing the array in place is not an alternative either: a stale closure over the same array object reads the mutated content, and Object.is would not fire for the explicit dependency either way. There is therefore no input where deleting the dependency changes behaviour, and react-hooks/exhaustive-deps under CI's --max-warnings 0 stays the operative guard — which is what the finding's own witness already measured.
Two consequences worth stating plainly:
- The dependency itself stays. It is lint-required, and it is correct on its own terms. This is a decline of the test, not of the dependency.
- I removed the drafted test rather than ship it. A test that is green both with and without the change witnesses nothing; keeping it would have added ~40 test lines and grown the diff while leaving the acceptance criterion unmet.
The redundancy is indirect, and I recorded that on the thread so it is not lost: it holds only while useExportCompletion keeps deriving from slashCommands. If that derivation is ever removed, the explicit entry becomes load-bearing and lint would again be the only guard.
Verification
npm run build— passed (exit 0; run twice — once to create the missing workspacedist/prerequisites a fresh checkout lacks, once after the final tree)npm run typecheck— passednpm run lint— passed (eslint . --ext .ts,.tsx && eslint integration-tests, no warnings)npx vitest run src/ui/components/InputPrompt.test.tsx(packages/cli, touched file) — 215 passed (215), matching the pre-round count after the drafted test was withdrawnnpx prettier --check packages/cli/src/ui/components/InputPrompt.test.tsx— passed- Mutation probe on the drafted R1-1 test — did not go red with the dependency deleted; instrumented to
PROBE slashCommands.length=4, proving the closure was not stale. Probe instrumentation and the dependency mutation were both reverted;git diffagainstHEADforInputPrompt.tsxis empty and the file is byte-identical to its committed state. - No settings source changed, so
npm run generate:settings-schemawas not applicable. - No integration run: the change is a comment inside a unit-test mock, not bundled-CLI behaviour.
git status --shortafter commit — clean; the commit contains onlypackages/cli/src/ui/components/InputPrompt.test.tsx.
Note on identity: the local git identity was unset in this checkout, so the commit initially failed with unable to auto-detect email address. I set user.name/user.email repo-locally to yiliang114 <1204183885@qq.com>, matching the author already on this branch's two prior autofix commits (1e5dd898, a0753101). No global config was touched and no history was rewritten.
中文说明
Autofix 轮次 —— PR #10940(issue #10935)
提交: 9cbd2e146f —— test(cli): note why the mock memory command needs an action(追加式提交,1 个文件,1 行新增)。
处理结论: 1 条行内意见已实现,1 条行内意见经探针取证后不予采纳,2 条 review 正文无需改动代码。本轮未做冲突合并 —— --conflict false,且自分叉点以来 main 上新增的两个提交都没有触及本 diff 中的任何文件。
各条反馈的处理
[rv:5104261338] CHANGES_REQUESTED(triage:"与 #10929 重复")—— 此前已处理,本轮无代码改动
它要求的动作 —— rebase 到 main 并把 PR 收窄到真正剩下的部分 —— 在本轮之前已经完成。分支现在位于 #10929(661f41ee)之上,相对 main 的 diff 只剩 mock 的 action: vi.fn() 一行以及 slashCommands 依赖项。@yiliang114 已在 [ic:5528927396] 中确认了这一收窄结果。review 状态本身只能由评审者解除。
这也回答了该 review 明确提出的问题:"如果你认为 #10929 仍漏掉了某个场景,请具体说明。" 提交路径上没有漏掉的场景。剩下的是 mock 漂移,而不是行为缺口:mock 的 memory 命令没有 action,而 #10929 的实时判定要求 commandToExecute?.action !== undefined(InputPrompt.tsx:1440-1443),这让 main 上两个已有的 /memory Enter 用例变红。真实的 memoryCommand 是有 action 的(packages/cli/src/ui/commands/memoryCommand.ts:18),因此修复只应落在 mock 上。
[rv:5105401415] COMMENTED(部分审查披露)—— 无代码动作
这是审查缺口的披露,而不是发现。逐条说明:
- CI 中 Integration Tests (CLI, No Sandbox) 被跳过。 本轮只改动了单元测试 mock 里的一行注释,没有触及任何打包后 CLI 的行为,因此不需要跑集成测试。
- Test Plan 中的路径
src/ui/components/InputPrompt.test.tsx报no such file or directory。 该路径是相对包目录的;文件实际存在于packages/cli/src/ui/components/InputPrompt.test.tsx,本轮也已运行。这只是 PR 正文的写法问题 —— 本轮无法编辑 PR 正文。 - flaky 二分与反向审计未完成。 本轮 diff 与两者都没有交集。
[rc:3927468069] R1-2(Suggestion)—— action: vi.fn() 看起来像装饰 → 已实现,线程已解决
在 mock 字段上方新增一行注释:
// InputPrompt's live-slash submit gate requires action !== undefined.
action: vi.fn(),这行注释是对照判定代码核实过的,而不是照抄意见:isLiveSlashCommand 要求 commandToExecute?.action !== undefined(InputPrompt.tsx:1440-1443),而对以斜杠开头的缓冲区,这个值会取代渲染发布的判定(isCurrentPerfectMatch),所以没有 action 的 mock 命令无法提交。评审者给出的 witness 对照(完整时 215 通过;还原该 hunk 后 2 个失败、Number of calls: 0)与此一致,@yiliang114 报告 main 上出现同样两个失败,也印证了这个耦合是真实存在的而非假设。这正是 AGENTS.md 注释规则所针对的情形:"为什么这里会有一个从未被调用的 vi.fn()"。
[rc:3927468061] R1-1(Suggestion)—— 新增测试钉住 slashCommands 依赖 → 不予采纳,已被探针证伪
该意见要求新增一个测试:当 slashCommands 从 useCallback 依赖数组中删除时变红。我写了这个测试并执行了验收标准。它不可能变红,因为它想钉住的"过期闭包"状态是不可达的:exportCompletion 同样是这个回调的依赖,而它的 identity 正是由 slashCommands 派生出来的。
派生链条,全部在本提交处逐行读过:
| 步骤 | 位置 |
|---|---|
exportFormatSuggestions = useMemo(..., [slashCommands]) |
useExportCompletion.ts:120-134 |
exportCycleFormats = useMemo(..., [exportFormatSuggestions]) |
useExportCompletion.ts:138-141 |
handleExportInput = useCallback(..., [buffer, exportCycleFormats, ...]) |
useExportCompletion.ts:201, 283-287 |
suggestionDisplayProps = useMemo(..., [exportFormatSuggestions, ...]) |
useExportCompletion.ts:320 |
exportCompletion = useMemo(..., [shouldShowSuggestions, suggestionDisplayProps, handleExportInput, ...]) |
useExportCompletion.ts:323-339 |
exportCompletion 位于按键回调的依赖数组中 |
InputPrompt.tsx:1951 |
因此,只要 slashCommands 这个 prop 的 identity 变化,按键回调就已经会被重建并捕获最新列表 —— 无论显式依赖项在不在。
探针,两个分支都在已放入该测试的情况下运行:
测试:先用去掉 'memory' 的 slashCommands 渲染(3 项)
-> 再用完整列表 rerender(4 项)
-> 在缓冲区已有 '/memory' 时按 Enter
依赖项存在: PASS - onSubmit('/memory')
依赖项删除: PASS - onSubmit('/memory') <- 验收标准未满足
在删除依赖项的情况下,于 Enter 时对判定内部打点:
PROBE slashCommands.length=4 <- 拿到的是最新列表,不是过期的 3 项
"原地替换数组内容"也不是另一条路:对同一个数组对象的过期闭包会读到被修改后的内容,而 Object.is 在两种情况下也都不会为显式依赖项触发。所以不存在任何输入能让"删除该依赖项"改变行为,而在 CI 的 --max-warnings 0 下,react-hooks/exhaustive-deps 仍然是真正起作用的守卫 —— 这也正是该意见自己的 witness 所测得的结论。
有两点需要明确说明:
- 依赖项本身保留。 它是 lint 所要求的,就其自身而言也是正确的。这里不予采纳的是测试,不是依赖项。
- 我删除了已起草的测试,而不是提交它。 一个在改动前后都是绿的测试什么都证明不了;保留它会让测试代码增加约 40 行、让 diff 变大,而验收标准依然未满足。
这种冗余是间接的,我已把这一点记录在线程上以免丢失:它只在 useExportCompletion 继续从 slashCommands 派生时成立。一旦这个派生关系被移除,显式依赖项就会变成关键项,而 lint 又将再次成为唯一的守卫。
验证
npm run build—— 通过(退出码 0;共运行两次 —— 一次用于生成新检出所缺的工作区dist/前置产物,一次在最终代码树上运行)npm run typecheck—— 通过npm run lint—— 通过(eslint . --ext .ts,.tsx && eslint integration-tests,无 warning)npx vitest run src/ui/components/InputPrompt.test.tsx(packages/cli,被触及文件)—— 215 通过(215),在撤回起草的测试后与轮次前的数量一致npx prettier --check packages/cli/src/ui/components/InputPrompt.test.tsx—— 通过- 针对起草的 R1-1 测试做变异探针 —— 删除依赖项后没有变红;打点得到
PROBE slashCommands.length=4,证明闭包并不过期。探针打点与依赖项改动都已还原;InputPrompt.tsx相对HEAD的git diff为空,该文件与其已提交状态逐字节一致。 - 未改动任何 settings 源文件,因此
npm run generate:settings-schema不适用。 - 未跑集成测试:本次改动是单元测试 mock 里的一行注释,不是打包后 CLI 的行为。
- 提交后
git status --short—— 干净;该提交只包含packages/cli/src/ui/components/InputPrompt.test.tsx。
关于身份的说明:本次检出中本地 git 身份未配置,因此提交最初以 unable to auto-detect email address 失败。我在仓库级别把 user.name/user.email 设为 yiliang114 <1204183885@qq.com>,与本分支此前两个 autofix 提交(1e5dd898、a0753101)已有的作者一致。未改动任何全局配置,也未重写历史。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
🧵 Resolved all 1 selected review thread(s). · 已关闭全部选中的 1 条评审线程。
Re-review when you have a moment. After round 10 this bot stops and leaves the PR for a human. · 有空请复审;第 10 轮后本 bot 停止并将 PR 交给人工。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max-2026-09-02
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No code change this roundThe only new feedback since the last evaluation (2026-09-03T18:25:34Z) is one 1. "Not reviewed: reverse audit — stopped before round 1 by the review time budget"This is the reviewer disclosing its own unfinished work, not a finding. Since the
No change follows from this pass. 2. "Test Plan (not a blocker):
|
|
🐑 Merge conflict with main detected — dispatched the autofix loop to resolve it. / 检测到与 main 的合并冲突,已触发 autofix 处理。 |
Main's 69c4f1e (#10961) landed the same two changes this branch carries: the missing `slashCommands` dependency on the keypress callback, and the `action` the mock `memory` command needs to satisfy the live-slash submit gate. Both are fallout from #10929, which is what this branch had been reduced to. Git reported a conflict only in the test fixture. In InputPrompt.tsx the two sides inserted `slashCommands` at adjacent but different positions, so the auto-merge silently kept both and produced a duplicate entry in the dependency array. Neither eslint's exhaustive-deps rule, which reports missing dependencies rather than duplicated ones, nor tsc flags that, so it would have landed unreviewed. Resolved to main's content, leaving one entry. The fixture resolves to the union: main's `action: vi.fn()` plus the comment recording why that never-invoked, never-asserted fn is load-bearing. The comment stays because it implements an accepted review suggestion and is still accurate against the merged gate, which remains `commandToExecute?.action !== undefined && args.length === 0 && canonicalPath.length === commandPartCount`. The functional fix now belongs entirely to main; this branch's remaining delta is that one comment line.
|
🤖 Addressed the latest review feedback (round 2/10). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/10 轮)。改动内容与我反驳保留之处如下: Autofix round — PR #10940 (issue #10935): merge-conflict resolutionFeedback this roundThere was no new review feedback. Every actionable section of Growth context was informational only (source 0 / test 1 net lines against What conflicted, and why
Both are the same fallout from #10929 that this branch had already been reduced The defect the auto-merge introduced silentlyGit reported a conflict only in the test fixture. Because the two sides inserted the same dependency at adjacent but different Resolution
The comment was the one real judgment call, so the reasoning is recorded here. I re-verified its claim against the merged code rather than trusting it, isLiveSlashCommand =
commandToExecute?.action !== undefined &&
args.length === 0 &&
canonicalPath.length === commandPartCount;So Net effect: the branch diff against Note for the maintainer — this PR is supersededFunctionally there is nothing left to merge. #10961 already carries both Finding dispositions
Mutation probeThe per-guard witness rule does not bind this round: the merge commit adds no The fixture line the preserved comment documents is already witnessed by VerificationCommands actually run, on the exact tree that was committed:
Not applicable this round:
One environment note, recorded rather than skipped: the repo's 中文说明Autofix 轮次 —— PR #10940(issue #10935):合并冲突解决本轮反馈本轮没有任何新的评审反馈。 增长上下文仅为信息性(源码净增 0 行 / 测试净增 1 行,预算 400/400)。没有 冲突是什么,为什么会冲突
两者都是 #10929 遗留的同一批后果,而本分支此前已被缩减到只剩这些内容,因此 自动合并悄悄引入的缺陷Git 只在测试 fixture 中报告了冲突。 由于两侧把同一个依赖插入到了相邻但不同的位置,三方合并把两处都保留了, 解决方式
这行注释是本轮唯一真正需要判断的地方,因此把推理记录在此。我第一遍解析时 我针对合并后的代码重新核实了它的论断,而不是直接采信,因为 #10961 有可能 isLiveSlashCommand =
commandToExecute?.action !== undefined &&
args.length === 0 &&
canonicalPath.length === commandPartCount;所以 最终效果:本分支相对 给维护者的提示 —— 本 PR 已被取代从功能上讲,已经没有任何东西需要合并。#10961 已把两处改动都带进了 各项意见的处置
变异探针"每个守卫都要有见证"这条规则本轮不适用:这个合并提交没有新增任何守卫、 被保留注释所说明的那行 fixture,已由本轮未改动的既有覆盖所见证:R1-2 的评审 验证情况在实际提交的那棵树上真实执行过的命令:
本轮不适用:
一条环境说明,记录在此而非略过:仓库的 Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。 🧵 Resolved all 1 selected review thread(s). · 已关闭全部选中的 1 条评审线程。 Re-review when you have a moment. After round 10 this bot stops and leaves the PR for a human. · 有空请复审;第 10 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R3-1 PR's claimed fixes already landed on main via #10944/#10961 — already reported (comment 3927468069)
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
已审查。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🔀 Base updated: red check(s) [Dependency CVE audit, Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Dependency CVE audit, Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round — PR #10940 (issue #10935): no code changeOutcome: no commit, no working-tree change. The round's only actionable item is the red Feedback this round
No retry context, no Why the red Test check cannot come from this PR1. What this PR changes. Against its own One inserted line, zero deleted, one file. The line is —a 2. What changed since the last verified round. Against round 2's commit Both arrived from So the surface the Test job runs at 3. Everything else in the same CI run is green on this SHA (run What actually failed: the missing-
|
| 章节 | 内容 |
|---|---|
| Diff 增长 | 源码净增 0 行 / 测试净增 0 行(预算 400/400,此前 0 轮超预算)—— 仅为信息性 |
## Reviews |
rv:5108052605(COMMENTED,第 3 轮)与 rv:5108360326(COMMENTED,第 4 轮)。两者的 ledger 都记录零条发现(findings:[]、posted:0、fresh:0) |
## Inline comments |
空 |
## Issue-level comments |
空 |
## Failed checks |
Test (ubuntu-latest, Node 22.x): FAILURE —— 唯一可执行条目 |
## Still-red checks |
空 |
没有重试上下文,没有 Deferred non-Critical feedback 章节(因此不是 critical-only 模式),也没有 Growth audit required 章节(因此未产出 growth-audit.json)。--conflict false,所以没有合并任何内容。
为什么这个红色 Test 检查不可能来自本 PR
1. 本 PR 改了什么。 与其自身的 main 父提交 60161cb64a 相比:
$ git diff --numstat 60161cb64a HEAD
1 0 packages/cli/src/ui/components/InputPrompt.test.tsx
一行新增、零行删除、一个文件。这一行是
// InputPrompt's live-slash submit gate requires action !== undefined.
—— 位于 mockSlashCommands 对象字面量内部、action: vi.fn(), 上方的一条 // 注释。它不可执行,任何测试的行为都不可能依赖它。我没有直接相信它,而是重新核验了它的断言:InputPrompt.tsx:1440-1443 仍然写着 isLiveSlashCommand = commandToExecute?.action !== undefined && args.length === 0 && canonicalPath.length === commandPartCount;。
2. 自上一个已验证轮次以来改了什么。 与第 2 轮的提交 6fd9daf07c(那一轮已用 build、typecheck、lint 和聚焦测试全部验证为绿)相比:
$ git diff --numstat 6fd9daf07c HEAD
35 0 integration-tests/test-helper.test.ts
13 0 integration-tests/test-helper.ts
两者都来自 main(60161cb64a),由 shepherd 的 update-branch 合并带入,而 Test 作业从不执行 integration-tests/:它不在根 workspaces 数组中(只有 integrations/external-context* 在),scripts/tests/vitest.config.ts 只包含 scripts/tests/**/*.test.{js,ts},而 test:integration:* 只出现在独立的 integration_no_ak(ci.yml:1735)和 integration_cli(ci.yml:1960)作业里。这些文件在同一 SHA 上由 Integration Tests (no-AK, No Sandbox) 执行过 —— SUCCESS。
因此 Test 作业在 4076bc7ee5 上运行的面,与它在 6fd9daf07c 上运行的面完全相同,没有任何增加。
3. 同一次 CI 运行中,该 SHA 上其它一切都是绿的(运行 33823654881):Lint & Static SUCCESS(该通道负责 eslint --max-warnings 0、tsc 以及 settings schema 新鲜度检查)、Integration Tests (no-AK) SUCCESS、Desktop Shell ubuntu-22.04 与 windows-2022 SUCCESS、web-shell E2E Smoke SUCCESS、TUI parity snapshots 与 OpenTUI no-flicker gate SUCCESS、Dependency CVE audit SUCCESS、Secret scan SUCCESS。只有一个作业是红的。
真正失败的地方:缺失 zip 的守卫
Test 作业的测试步骤(ci.yml:679,timeout-minutes: 110)按顺序运行两条命令:
npm run test:ci:workspaces -- --retry=2 # 非零退出 -> 步骤在此结束
npm run test:scripts -- --retry=2 # 仅当上一条通过时才会执行
耗时说明第一条通过了。 该作业运行于 00:54:45Z → 02:28:37Z = 93.9 分钟。它的 timeout-minutes 在托管运行器上是 60,在 ecs-qwen 池上是 120(ci.yml:376),所以托管运行不可能达到 94 分钟 —— 这是 ECS 通道,而 110 分钟的步骤上限和 120 分钟的作业上限都没有触发,说明该步骤是自行以非零退出的。ci.yml 自己给出了健康工作区阶段的量级:"~32 minutes was measured at six Vitest forks; the pool now runs three, which roughly doubles the test phase on an idle host, and a failing run adds its retries on top."(六个 Vitest fork 时测得约 32 分钟;池现在跑三个 fork,在空闲主机上大致使测试阶段翻倍,而失败的运行还会叠加重试。)即约 64 分钟,在约第 18 分钟进入该步骤 → 约 82 分钟,而作业在 93.9 分钟结束。如果失败发生在工作区那一半,npm 会在第一个失败的工作区停止 —— 而 packages/cli 排得很靠前(我本地观察到的顺序是 acp-bridge → audio-capture → chrome-extension → cli),在同样庞大的 packages/core 以及工作区列表的其余部分之前 —— 那么该步骤会早几十分钟就结束。红色出现在步骤的末尾。
我复现了这个末尾。 使用 CI 测试步骤自身的环境(CI=true、全新 HOME、ECS 运行器名、跳过延迟预算、四个 API key 全部置空、--retry=2)运行 npm run test:scripts:
Test Files 1 failed | 75 passed (76)
Tests 1999 passed (1999)
⎯⎯ Failed Suites 1 ⎯⎯
FAIL scripts/tests/install-script.test.js [ scripts/tests/install-script.test.js ]
Error: `zip`/`unzip` missing on a CI host; archive tests would skip.
❯ scripts/tests/install-script.test.js:56:9
注意这个形态:一个文件失败,而所有跑过的测试都通过了。 这是收集阶段在模块作用域抛出的异常,不是断言失败。scripts/tests/install-script.test.js:51-59 是:
const zipAvailable =
process.platform === 'win32' ||
(spawnSync('zip', ['--version']).error === undefined &&
spawnSync('unzip', ['-v']).error === undefined);
if (process.env.CI && process.platform !== 'win32' && !zipAvailable) {
throw new Error('`zip`/`unzip` missing on a CI host; archive tests would skip.');
}这是刻意设计的,ci.yml 在两处记录了它:"The install-script packaging suite needs zip/unzip, and throws on a CI host that ships neither, so a silent skip there is impossible"(install-script 打包测试套件需要 zip/unzip,在两者都没有的 CI 主机上会抛错,因此那里不可能静默跳过),以及提供这些二进制的那个步骤的警告文本 —— Install tmux and zip tooling,它是 continue-on-error: true,步骤上限 5 分钟、每次 apt-get 调用限时 140 秒 —— "tmux/zip install failed; real-tmux capture tests will be skipped and the zip-packaging suite will throw on CI."(tmux/zip 安装失败;real-tmux 抓取测试将被跳过,而 zip 打包套件会在 CI 上抛错。)
因此最可能的根因是:运行器的 zip/unzip 安装没有提供 zip,归档安全守卫按设计触发 —— 而这发生在约 76 分钟本来全绿的测试之后。 这是运行器工具链问题,与测试 fixture 里的一行注释毫无关系。
用 CI 未设置的相同命令重跑 —— 这让守卫做它在任何最小镜像上都会做的事,即跳过归档用例而不是抛错 —— 这一半完全变绿:
Test Files 76 passed (76)
Tests 2109 passed | 16 skipped (2125)
### test:scripts (CI unset) RC=0
这 16 个跳过是依赖 zip 的归档用例。被那次抛错压制的套件正是 install-script.test.js 自己的:2125 - 1999 = 126 个测试,其中 110 个通过、16 个跳过 —— 前提是允许该文件加载。
对该诊断的诚实限制。 本环境没有 GH_TOKEN/GITHUB_TOKEN,因此 gh run view --log-failed 不可用,我无法从运行 33823654881 中读到失败的那一行。这个认定依据的是作业 93.9 分钟的耗时与检查拓扑,加上一次确定性的本地复现,其失败签名恰好是该步骤后半段所需的那种:步骤失败,而所有已执行的测试都通过。这是一个有力的推断,不是日志阅读。
我发现的另一个机制,以及为什么它不是答案
我第一次全量套件运行在工作区那一半也产生了一个失败:
❯ src/commands/serve.test.ts (70 tests | 1 failed) 188591ms
× serve startup import boundary > reaches listening through the dev entrypoint
without loading interactive Ink internals first 184159ms (retry x2)
→ serve did not reach listening
那是墙钟预算,不是缺陷:该测试启动 scripts/dev.js serve(tsx 开发入口)并等待 qwen serve listening on,预算为 startupMs = ecs ? 60_000 : 30_000(serve.test.ts:1045-1047)。三次约 61 秒的尝试 = 60 秒的 ECS 预算被吹爆三次。
它是资源竞争,有三方面证明:
- 在同一主机、同一环境下单独运行时它通过:
npx vitest run src/commands/serve.test.ts --retry=2→ 70/70 通过,而先前超时的测试用时 20116ms,其预算为 60000ms。两次运行期间主机负载均值约为 197-206;唯一变化的是 16 个并发 vitest worker 对 1 个。 - 它与 base 逐字节相同:
git rev-parse 60161cb64a:…/serve.test.ts HEAD:…/serve.test.ts→ 两者都是fad8ae165a025006f41f188f97715091e005a448,且git diff 60161cb64a HEAD -- packages/cli/src/commands/为空。AGENTS.md 要求的"在 base 分支上复现"以其最强形式被满足:base 与分支运行的是同一个文件。 - 它是已知的、已被调优过的竞争受害者:
git log -- packages/cli/src/commands/serve.test.ts显示3aa1b14624 ci: stabilize tests under shared ECS host contention (#10552),而git show确认正是该提交加入了那几行ecs ? 60_000 : 30_000。
但它不是 CI 的失败原因:它会提前中止工作区那一半,产生远短于 93.9 分钟的作业。我之所以报告它,是因为它是该测试在重载主机上的真实敏感性,也是因为是我自己的运行诱发了它 —— 我的本地运行使用了配置里的 maxWorkers: '25%'(在这台 64 核主机上是 16 个 worker),而 ECS 通道通过 VITEST_MAX_FORKS 限制 fork 数(取自 vars.QWEN_CI_VITEST_MAX_WORKERS,默认为 4;ci.yml 自己的量级注释说该池现在跑三个)。我的复现比 CI 更并行,所以这个失败是我的,不是运行器的。
作为记录,我工作期间该池确实很忙 —— 这台 64 核主机的 /proc/loadavg 在本轮中采样到 200.89、206.22、198.24、194.11 和 216.17,并且在我自己的 worker 全部退出后仍高于 150。
我刻意没有改动的内容
- 没有改
.github/workflows/ci.yml。 如果重跑后仍以同样方式变红,持久的修复位于Install tmux and zip tooling步骤 —— 它是continue-on-error: true,所以一次失败的apt-get只换来一条警告,然后是约 76 分钟的测试,最后守卫才在末尾触发。改为在该步骤之后立刻检查zip就能快速失败。那是本 PR 从未涉及的 CI 机制,本轮也不被允许修改。在此标记给维护者,作为可执行的后续项。 - 没有弱化
scripts/tests/install-script.test.js中的守卫。 它是正确且有意为之的 —— 它的存在是为了让没有归档二进制的 CI 主机无法静默跳过归档安全用例。为了让检查变绿而删除它,是用真实的安全网换取虚假的通过。根scripts/也是本 PR 从未触及的区域,改它会扩张 footprint。 - 没有放宽
serve.test.ts里的startupMs/testMs。 超出本 PR 的主线目的,已由 ci: stabilize tests under shared ECS host contention #10552 负责,而且我没有 CI 日志证明是那个测试让 CI 变红。凭一次本地观察去猜一个新的共享池预算,正是预算在没有证据的情况下不断上调的方式。 - 没有合并
origin/main。--conflict false。main前进了两个提交(b7815a7e1a、d4e3e4fc87),都没有触及本 diff 中的文件;它们唯一会带来的文本差异是ci.yml的HELPER_TESTS增加.github/scripts/e2e-build.test.mjs,而那是本 PR 未触及的通道。 - 没有需要通过
deferred-findings.json延后的内容。 上面两个机制都是环境类发现,且都不带三个反馈来源之一的 id(## Failed checks那一行没有rc:/rv:/ic:句柄),所以没有可用的合法条目键。它们被记录在此处、维护者会读到的地方,而不是被静默丢弃。
其它反馈条目
-
rv:5108360326自己说明了降级原因:"⚠️ Downgraded from Approve to Comment: CI still running."(已从批准降级为评论:CI 仍在运行。)评审者的底线本来是 APPROVE;唯一拦住它的是进行中的 CI,而该 CI 之后因上述工具链原因变红。rv:5108052605同样是零条发现。两者都没有要求代码改动。 -
R3-1("PR's claimed fixes already landed on main via Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944/fix(cli): complete the live slash-submit deps and fixture (#10944) #10961",本 PR 声称的修复已通过 Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944/fix(cli): complete the live slash-submit deps and fixture (#10944) #10961 落到 main)在反馈中被列为已报告过、不再重复。第 2 轮已把其后果摆在维护者面前(评论
5533603446:"Functionally there is nothing left to merge… Merging that single comment is fine, and closing this PR is equally fine",功能上已无可合并内容……合并那一行注释可以,关闭本 PR 也同样可以)。没有新内容可补充;该选择属于维护者,且仍然悬而未决。 -
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory出现在全部四条评审正文中。这是 PR 描述的缺陷,不是代码的缺陷:该路径相对于packages/cli,而评审者从仓库根目录运行它。本轮我无法编辑 PR 正文(没有 GitHub 凭据;PR 写入由工作流负责)。能正确解析的命令是cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx→ 215 passed。在此标记,以便维护者修正正文。
建议
重新运行 Test (ubuntu-latest, Node 22.x) 检查。 重跑会重新尝试 zip/unzip 安装,而那正是可能的原因。仓库已经有对应机制:qwen-ci-flaky-rerun.yml(Qwen CI Failure Patrol,每 10 分钟一次,STALE_MINUTES: 30)会分类陈旧的 PR 失败并在有瞬态基础设施证据时重跑 —— 本次失败完成于 02:28:37Z,在约 02:58:37Z 达到该巡查 30 分钟的陈旧阈值 —— 也就是本轮于约 02:54Z 被派发之后的几分钟。如果它以同样的"1 个文件失败 / 所有测试通过"签名再次变红,那么该修的是 Install tmux and zip tooling 步骤,而不是本分支。
验证
本轮实际运行的命令,均在 HEAD = 4076bc7ee5 上执行,之前、期间、之后都没有源码改动:
npm run build—— 通过,退出码 0。它还重新生成了packages/vscode-ide-companion/schemas/settings.schema.json,与已提交副本逐字节相同(之后git status --porcelain为空),这就是 settings schema 新鲜度的证据。cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx(CI 测试步骤环境)—— 通过:Test Files 1,Tests 215 passed (215),退出码 0。这是 PR 唯一触及的文件。cd packages/cli && npx vitest run src/config/settings.test.ts(同一环境,已清除 agent 沙箱标记)—— 通过:187/187,退出码 0。npm run test:ci:workspaces -- --retry=2(CI 测试步骤环境,已清除标记)——packages/acp-bridge34 文件 / 1919 测试通过;packages/audio-capture1 文件 / 2 通过;packages/chrome-extension7 文件通过、1 跳过 / 76 通过、3 跳过;packages/cli跑到 其 1003 个测试文件中的约 425 个,恰好一个失败测试(serve startup import boundary,已在上面诊断)。在该点刻意停止 —— 原因记录在下方。cd packages/cli && npx vitest run src/commands/serve.test.ts --retry=2(同一环境,单独运行)—— 通过:70/70,退出码 0;先前失败的测试用时 20116ms。npm run test:scripts -- --retry=2(CI 测试步骤环境)—— 失败:Test Files 1 failed | 75 passed (76),Tests 1999 passed (1999),Duration 335.56s。原因:scripts/tests/install-script.test.js:56抛出`zip`/`unzip` missing on a CI host,因为本沙箱容器不提供zip(command -v zip→ 不存在;unzip存在;tmux不存在)。npm run test:scripts -- --retry=2(CI未设置,使守卫跳过而不抛错)—— 通过:Test Files 76 passed (76),Tests 2109 passed | 16 skipped (2125),退出码 0。git status --porcelain—— 为空;git rev-parse HEAD——4076bc7ee5f26de19b1325d24f1e9ea3f3501275,未变。没有创建提交。
为什么全量套件运行被中止而不是跑完(记录在案,而非跳过):在 packages/cli 的 425/1003 个文件处它已耗时约 18 分钟,主机负载约 200;而 npm 在一个工作区失败后会中止其余工作区,所以继续跑既不会带来 core/web-shell/其余部分的覆盖,也不会带来除更多同类竞争性墙钟假阳性之外的任何信号 —— 而每一个假阳性都需要自己的单独重跑才能排除。继续向一个已超过 2.5 倍超售的共享池添加 16 个 worker,还会拖慢与它共享的 CI 作业。结论并不依赖缺失的覆盖:在该 SHA 上 Test 作业执行的每个文件都与 main@60161cb64a 逐字节相同,只差一行注释,所以未运行包中的任何确定性失败都会是 main 的缺陷,并会以同样方式上报。
未运行的命令及原因:
npm run typecheck、npm run lint—— 本轮无代码改动,且 CI 的Lint & Static (ubuntu-latest, Node 22.x)在这个确切 SHA 上是 SUCCESS;该通道负责--max-warnings 0的 eslint、tsc以及 settings schema 新鲜度检查。在负载约 200 的主机上本地重跑它们只会增加竞争,不会增加证据。npm run generate:settings-schema—— 没有 settings 源发生改动(settingsSchema.ts/settings.ts未被触及);build 自身的 schema 生成让工作区保持干净,这就是同样的新鲜度证据。npm run bundle之后的集成测试 —— 被触及的行为由上面的单元测试套件直接覆盖,而不是只能通过打包后的 CLI 覆盖;并且Integration Tests (no-AK, No Sandbox)在该 SHA 上是 SUCCESS。- 变异探针 —— 不适用:本轮没有新增守卫、分支或行为,也没有创建提交,因此没有需要见证的新内容。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max-2026-09-02
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R5-1 description claims fixes that already landed on main via #10944/#10961 — already reported as R3-1 (round-3 review 5108052605, re-confirmed in round-4 review 5108360326)
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round — PR #10940 (issue #10935): no code changeOutcome: no commit, no working-tree change. The round has exactly one red check and one re-reported Suggestion. Neither has a code fix, and I can prove the red check is not this PR's: the gate's only repository inputs are byte-identical across a red verdict, a green verdict, and the current red — and identical to current Feedback this round
No retry context, no 1.
|
| 章节 | 内容 |
|---|---|
| Diff 增长 | 源码净增 0 行 / 测试净增 0 行(预算 400/400,此前 0 轮超预算)—— 仅为信息性 |
## Reviews |
rv:5109659820(COMMENTED,第 5 轮,sha 043681b8a8)。其 ledger 记录零条发现(findings:[]、posted:0、fresh:0);只是重新列出一条已报告过的建议(R5-1 即 R3-1),并披露了两处审查缺口 |
## Inline comments |
空 |
## Issue-level comments |
空 |
## Failed checks |
Dependency CVE audit: FAILURE —— 唯一可执行条目 |
## Still-red checks |
空 |
没有重试上下文,没有 Deferred non-Critical feedback 章节(因此不是 critical-only 模式),也没有 Growth audit required 章节(因此未产出 growth-audit.json)。--conflict false,所以没有合并任何内容。
1. Dependency CVE audit —— 红色,且可证明与本 PR 无关
该作业做什么(.github/workflows/security-checks.yml:26-64):先 npm ci --ignore-scripts --no-audit,再对根工作区执行 npm audit --omit=dev --audit-level=high,然后对每个 packages/*/package-lock.json 重复同样两步(跳过 packages/mobile-mcp,workflow 注明它是 vendored、不会被直接安装)。这是硬门禁:任何 high 级别 CVE 都会让它失败。因此它在仓库侧的唯一输入就是根 lockfile、两个 vendored lockfile、各 package.json 清单,以及 .nvmrc。
这些输入在一次红、一次绿、再一次红的判定之间完全相同。 下面是 blob 哈希,而不是 diff 摘要:
$ for s in 6fd9daf07c 4076bc7ee5 043681b8a8 origin/main; do …git rev-parse $s:<lockfile>…; done
6fd9daf07c root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← CVE audit 红 (~00:28Z)
4076bc7ee5 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← CVE audit 绿 (run 33823654881)
043681b8a8 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← CVE audit 红 (run 33837912929, 04:44:25→04:55:43Z)
origin/main root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← 当前 main (8a0a9c6614)
四棵树的三个 lockfile 完全一致。git diff --numstat HEAD origin/main -- package-lock.json '**/package-lock.json' 为空;本分支 merge base(56f75adf29)与 origin/main 之间的 package.json 同类 diff 也为空。在 6fd9daf07c 的红与 4076bc7ee5 的绿之间,唯一发生变化的文件是 integration-tests/test-helper.ts(+13)和 integration-tests/test-helper.test.ts(+35)—— 没有任何清单文件、lockfile 或 .nvmrc。
而 PR 本身:
$ git diff --name-only origin/main...HEAD
packages/cli/src/ui/components/InputPrompt.test.tsx # 1 处新增,0 处删除
即在 mockSlashCommands fixture 中插入一行 // 注释,位于 main 自 #10961 起就已经带有的 action: vi.fn(), 之上。它不可执行,也不是依赖输入。
结论(这是证明,不是推断): 红色判定不能归因于本 PR,不能归因于本分支上的任何提交,也不是这里任何代码改动能修复的。main 拥有完全相同的输入,所以 main 自己的 push 也暴露在同样的判定之下。
机制(推断 —— 明确标注为推断,因为我读不到作业日志)。 相同输入在约 4.5 小时内产生 红 → 绿 → 红,说明判定由树外因素驱动。两个候选,都是外部的:
npm audit查询的漏洞通告数据在该窗口内发生了变化。这需要一条通告先发布、再被撤回或重新分级、然后再次发布 —— 可能,但形态上更不可能。- 作业自身的联网步骤因非 CVE 原因返回了非零退出码。workflow 用
npm audit … || status=$?累积状态,对每个 vendored 包用( cd … && npm ci … && npm audit … ) || status=$?,最后exit "$status"。这会把npm ci或npm audit的 registry/EAI_AGAIN/ENOTFOUND失败与真实 high 级别发现完全等同处理 —— 门禁在根本没有任何 CVE 的情况下也会变红。两个 vendored lockfile 各自需要一次冷启动npm ci(作业的cache: npm是按根 lockfile 做 key 的),在托管 runner 上、15 分钟上限之内完成;失败的那次运行用了 11 分 18 秒。
我在这里无法区分二者:本环境没有 GH_TOKEN/GITHUB_TOKEN,所以无法对 run 33837912929 执行 gh run view --log-failed;而 npm audit 属于联网的包管理命令,本轮不允许执行。区分它们只需要看一眼日志 —— 出现 high 级别表格就是真实通告;出现拉取/安装错误就是基础设施问题。
无论哪种情况,都不存在范围内的修复。 清掉一条真实通告意味着改变依赖版本,也就是编辑 package.json/package-lock.json。lockfile 与 patches/ 属于本轮不得触碰的供应链区域,而且根 lockfile 在本 PR 的 footprint(一个测试文件)之外,footprint 门禁同样会拒绝这种扩张。仓库对这件事已有正确的形态:git log -- package-lock.json 显示出 2a428054c4 chore(deps): bump fast-uri to 3.1.7 to clear the high-severity audit gate (#10862) —— 一个针对 main 的独立 chore(deps) 版本提升。反之,如果日志显示是网络失败,补救办法就是重跑,而这正是 qwen-ci-flaky-rerun.yml(Qwen CI Failure Patrol,每 10 分钟,STALE_MINUTES: 30)存在的意义;该作业于 04:55:43Z 完成,约在 05:25:43Z 越过了该 patrol 的陈旧阈值。
有一个陷阱值得向 shepherd 指出。 00:54:12Z 的 base 更新(ic:5534112137)看到这个检查红色,确认它在 main 上是绿的,于是合并了 main —— 之后这里变绿了。但合并不可能是变绿的原因:两侧的清单文件本来就已经逐字节相同,而合并唯一带进来的文件就是那两个 integration-tests/ helper。变绿来自作业被重新执行了一次。所以「合并当前 main」对这个检查不是补救手段,围绕它派发 base-update 或 autofix 轮次只会持续消耗预算而无法推动它。
2. R5-1 / R3-1 —— 「描述声称的修复已通过 #10944/#10961 落到 main」
已核实为真。这是 PR 正文(body)的缺陷,不是代码的缺陷,因此没有代码改动能解决它 —— 而真正能解决它的那个改动我也无法做出:
- 实质内容已经确立,并且在当前树上再次确认:
main的69c4f1e4bb—— fix(cli): complete the live slash-submit deps and fixture (Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944) (fix(cli): complete the live slash-submit deps and fixture (#10944) #10961) —— 做出了本分支此前被缩减后要做的两处改动(slashCommands的useCallback依赖,以及 mock 的action: vi.fn())。git diff origin/main...HEAD现在只返回一行注释,这就是全部剩余增量。 - address-review 模式没有 PR 正文这一输出。我是去查了 workflow 而不是靠假设:
pr-body.md只被 develop-issue 的发布作业消费(qwen-autofix.yml:1386、:1438、:1545——gh pr create --body-file),而 address-review 的 artifact 清单(qwen-autofix.yml:5829)并不包含它。写一个出来只会成为无人读取的游离文件。本轮同样按设计没有 GitHub 凭据。 - 剩下的是维护者的选择,自第 2 轮以来没有变化:要么修正正文,使其只描述这一行注释;要么以「已被取代」为由关闭 PR。@yiliang114 的缩减说明(ic:5528927396)——「This PR now adds that one line and updates the description accordingly」—— 指向前者;第 2 轮(ic:5533603446)记录了两者都可接受。我不会在代码里替这个决定收尾,也就是不会通过删掉注释让 diff 消失:那等于单方面选择「关闭」,而这行注释正是审查者 R1-2 被采纳后的处理结果。
上报(escalate)—— 既不驳回,也不转入后续队列。 驳回是错的(发现属实)。转入 follow-up 队列也是错的:陈旧的 PR 正文不会存活到合并之后,等维护者从队列里取出这条时已经无事可做。它需要在本 PR 上得到答复,所以留在这里保持可见。
3. 「Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory」
五份审查正文都提到了它;这同样是 PR 正文的缺陷,而非代码缺陷。Test Plan 里的路径是相对于 packages/cli 的,而审查者是从仓库根目录解析的。可用的命令是:
cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx
本轮在 043681b8a8 重新执行:215 passed (215)。修正正文的人应把路径改成相对仓库根的 packages/cli/src/ui/components/InputPrompt.test.tsx,或保留 cd 前缀 —— 这也能让这条提示不再每轮重复出现。
4. 反向审计(reverse audit)—— 审查者未能执行的那一遍
rv:5109659820 披露:「Not reviewed: reverse audit — stopped before round 1 by the review time budget.」这是审查者自己未完成的工作,而不是一条发现,但它意味着当前 SHA 上有一遍检查谁都没跑过,所以我来跑。自上次本地验证以来 main 已合并 #10986/#10987,它们改动了 OpenTUI 的 input prompt,因此我是把 diff 对照 043681b8a8 的确切代码来核对,而不是相信前几轮的结论:
- 注释的陈述仍然准确。
InputPrompt.tsx:1440-1443是isLiveSlashCommand = commandToExecute?.action !== undefined && args.length === 0 && canonicalPath.length === commandPartCount;,所以action !== undefined确实是实时斜杠提交路径的前置条件。 - 它所注解的那一行仍然是关键依赖。 依赖它的两个用例在此 SHA 上都存在 ——
InputPrompt.test.tsx:2843-2858(「Enter must submit/memory, NOT autocompleteshow」)与:2874-2886—— 且整个文件 215/215 通过。 - 没有可删除的东西。
mockSlashCommands中没有重复的说明注释,没有死代码,没有冗余。相邻的clearmock 带着未加注释的action: vi.fn(),这是正确而非不一致:只有/memory的 Enter 路径对该判定敏感,而 R1-2 要求把说明正好放在它现在所在的位置。
验证(Verification)
本轮在 HEAD = 043681b8a8 上实际执行的命令,执行前、执行中、执行后均未改动源码:
git diff origin/main...HEAD/--numstat/--name-only—— 本 PR 为 1 个文件、1 处新增、0 处删除:packages/cli/src/ui/components/InputPrompt.test.tsx。git diff --numstat HEAD origin/main -- package-lock.json '**/package-lock.json'—— 空。git diff --numstat 56f75adf29 origin/main -- package.json '**/package.json' package-lock.json '**/package-lock.json'—— 空;自本分支 merge base 以来main也没有动过任何清单文件。git diff --numstat 6fd9daf07c 4076bc7ee5—— 2 个文件,都在integration-tests/,无清单文件:红→绿翻转时输入相同。git diff --numstat 4076bc7ee5 HEAD—— 15 个文件,全部来自main(.github/、docs/design/、packages/cli/src/ui/opentui/、packages/web-shell/、scripts/tests/),无清单文件:这次合并里没有任何东西能解释 绿→红 翻转。- 对
6fd9daf07c、4076bc7ee5、043681b8a8和origin/main执行git rev-parse <sha>:<lockfile>,取三个被审计的 lockfile —— 全部相同(见 §1 引用)。 git log --oneline -n 3 -- package-lock.json—— 最后一次改动是9ffada4eac chore(release): v0.23.0 (#10914);同时暴露出补救先例2a428054c4 … (#10862)。npm run build—— 通过,退出码 0(在负载约 119 / 64 CPU 的共享主机上耗时 571 秒)。它同时重新生成了packages/vscode-ide-companion/schemas/settings.schema.json,与已提交副本逐字节相同 —— 之后git status --porcelain为空,这就是 settings schema 新鲜度的证据。cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx—— 通过:Test Files 1,Tests 215 passed (215),退出码 0,98.5 秒。这是本 PR 唯一触碰的文件。(第一次尝试被仓库自身的 vitestglobalSetup守卫拦下,它指出缺少工作区dist/产物;npm run build正是该守卫给出的修复方式,上面是重跑的结果。)git status --porcelain—— 每一步之后都为空;git rev-parse HEAD——043681b8a82f08051ab8c9036f06de104a3064ae,未变。没有创建任何提交。 测试运行产生的packages/cli/junit.xml与coverage/已被 gitignore(.gitignore:82、.gitignore:128),因此工作区保持干净。
未执行的命令及原因:
npm audit --omit=dev --audit-level=high—— 失败检查自身的命令。它是联网的包管理命令,本轮不允许执行;而且它不会改变处置结论:清单文件与main相同,所以它无论指向什么,都是main侧的状况。npm run typecheck、npm run lint—— 本轮无代码改动、无提交,而Lint & Static (ubuntu-latest, Node 22.x)在这个确切 SHA 上是 SUCCESS;该 lane 负责 eslint(--max-warnings 0)、tsc以及 settings schema 新鲜度检查。npm run generate:settings-schema—— 没有改动 settings 源(settingsSchema.ts/settings.ts未触碰);build 自身的 schema 生成让工作区保持干净,这就是同样的证据。npm run bundle之后的集成测试 —— 被触碰的文件是单元测试 fixture,已由上面的定向运行直接覆盖,而Integration Tests (no-AK, No Sandbox)在此 SHA 上是 SUCCESS。- 变异探测(mutation probe)—— 不适用:本轮没有新增任何守卫、分支或行为,也没有创建提交,因此没有新东西需要见证。(R1-2 要求的
action: vi.fn()见证已在第 1 轮探测过,而那一行现在位于main上。)
该 SHA 上其余检查全绿
在 043681b8a8:Test (ubuntu-latest, Node 22.x) SUCCESS(04:44:51→05:00:13Z —— 第 4 轮的红色已清除,与该轮「缺少 zip 的运行器工具链」诊断一致)、Lint & Static SUCCESS、Integration Tests (no-AK, No Sandbox) SUCCESS、Desktop Shell ubuntu-22.04 与 windows-2022 SUCCESS、web-shell E2E Smoke SUCCESS、TUI parity snapshots 与 OpenTUI no-flicker gate SUCCESS、Secret scan (TruffleHog) SUCCESS、Classify PR SUCCESS。46 个检查中恰好只有一个是红的:Dependency CVE audit。
我需要维护者做的事
- 重跑
Dependency CVE audit,然后读一次它的日志。 它的 lockfile 与main相同,也与第 4 轮在 04:23Z 报告时仍然为绿的那次运行相同,所以红色来自外部。日志中出现high级别表格,说明是真实通告 → 用针对main的chore(deps)版本提升来修(#10862的形态),而不是在本 PR 内部。出现 registry/安装错误,说明是基础设施问题 → 重跑即是修复。两者都无法从本分支触及,而且再次把main合并进来也无济于事(见 §1)。 - R5-1/R3-1 需要一个「修改 PR 正文」或「关闭 PR」的决定。 代码增量只有一行注释;描述却仍然声称那些已通过 Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944/fix(cli): complete the live slash-submit deps and fixture (#10944) #10961 落地的修复。两种处理都可接受,而本轮我无法执行其中任何一种。
- 正文里的 Test Plan 路径应改为
packages/cli/src/ui/components/InputPrompt.test.tsx,或保留其cd packages/cli前缀,使审查者的检查能够解析,并让这条提示不再重复出现。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max-2026-09-02
|
🔀 Base updated: red check(s) [Dependency CVE audit] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Dependency CVE audit] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round — PR #10940 (issue #10935): no code changeOutcome: no commit, no working-tree change. This round has two red checks and zero review findings. Neither red is attributable to this PR, and I can now name the mechanism for the Feedback this round
No retry context, no 1.
|
| SHA | Verdict | Window | Duration |
|---|---|---|---|
043681b8a8 |
SUCCESS | 04:44:51 → 05:00:13Z | 15m22s |
e097ba81ba |
FAILURE | 07:42:45 → 07:58:02Z | 15m17s |
Provenance, because the two windows are not equally fresh: the red run's window is read from this round's checks.json; the green run's window at 043681b8a8 comes from round 5's report, which read that run's check timings while they were current. I cannot re-read it now without GH_TOKEN, so it is reported-then rather than re-measured-now. The argument does not lean on the five seconds — it leans on both runs landing in the same ~15-minute band, against round 4's contended ECS run which took 93.9 minutes.
Same lane, same profile, and the red run consumed the same wall time as the green one — five seconds less. That matters because of how the test step is built (ci.yml:756-760):
npm run test:ci:workspaces -- --retry=2 # RC=$?
if [ "$RC" -eq 0 ]; then
npm run test:scripts -- --retry=2 # reached ONLY when workspaces passed
fi
A genuine test failure in the workspaces half makes npm stop at the first failing workspace, skipping every later workspace and all of test:scripts — the job would end substantially sooner than a green run. This one did not end sooner. It ran the whole distance and then failed. The only command positioned to do that is the step's last one, test:scripts.
(Round 4's red was a different shape entirely: 93.9 minutes on the contended ECS lane. That diagnosis was correct for that run and does not carry over to this one except in mechanism, which turns out to be the same.)
Reproduced that exact failure at HEAD
test:scripts' last-file guard throws at module scope on a CI host without zip. This sandbox is such a host — command -v zip → absent, command -v unzip → /usr/bin/unzip. Running the CI command with the CI flag set:
$ CI=true npm run test:scripts
FAIL scripts/tests/install-script.test.js [ scripts/tests/install-script.test.js ]
Error: `zip`/`unzip` missing on a CI host; archive tests would skip.
❯ scripts/tests/install-script.test.js:56:9
Test Files 1 failed | 75 passed (76)
Tests 2007 passed (2007)
SCRIPTS_RC=1
Read the shape: one file failed while every test that ran passed. That is a collection-time throw, not an assertion — precisely the signature a step needs in order to fail after all the real testing is already green. scripts/tests/install-script.test.js:49-59 is deliberate:
// Local minimal images may omit them, but CI must keep the archive-safety
// cases active.
const zipAvailable = process.platform === 'win32' || (spawnSync('zip', ['--version']).error === undefined && spawnSync('unzip', ['-v']).error === undefined);
if (process.env.CI && process.platform !== 'win32' && !zipAvailable) {
throw new Error('`zip`/`unzip` missing on a CI host; archive tests would skip.');
}Why the runner can be missing zip, and why nothing annotates it
Install tmux and zip tooling (ci.yml:639-676) is continue-on-error: true with timeout-minutes: 5 and 140-second bounds on each apt-get call. On failure it emits only a ::warning::, whose own text states the consequence: "tmux/zip install failed; real-tmux capture tests will be skipped and the zip-packaging suite will throw on CI." ci.yml documents the design twice: "The install-script packaging suite needs zip/unzip, and throws on a CI host that ships neither, so a silent skip there is impossible." So an apt hiccup on the runner turns a required check red after the whole suite has passed, with no failing annotation to point at it.
The guard's code did not change between green and red
Blob hashes, not a diff summary:
$ git rev-parse <sha>:scripts/tests/install-script.test.js
043681b8a8 b033e3d7f9… ← Test GREEN
e097ba81ba b033e3d7f9… ← Test RED
9c320cb0cc b033e3d7f9… ← merge base (pure main)
80497a74d0 b033e3d7f9… ← current main tip
And the 07:41 base-update merge that produced this SHA touched nothing that the Test job's tooling depends on:
$ git diff --numstat 043681b8a8 e097ba81ba -- scripts/ .github/
(empty)
Same guard, same workflow, same lockfiles — the only thing that can have changed is whether that particular runner had zip.
I ruled out a real failure in the merged main code
The 07:41 merge brought in ~100 files of main, so "main broke something" deserved a direct test rather than an argument from duration. Targeted runs at HEAD:
packages/acp-bridge— the first workspace in the run order and the largest test delta in the merge (bridge.test.ts+286,bridge.ts+147): 34 files / 1926 tests passed, exit 0.packages/cli src/serve/server.test.ts+src/commands/serve.test.ts— the single largest merged test file (server.test.ts+2413/-163): 2 files / 1291 tests passed, exit 0.- The PR's own file,
packages/cli src/ui/components/InputPrompt.test.tsx: 215 passed, exit 0. npm run build: exit 0, and it leftgit status --porcelainempty, so the regeneratedpackages/vscode-ide-companion/schemas/settings.schema.jsonmatches the committed copy (the merge brought main'ssettingsSchema.tsand schema changes with it — both already committed on main).
Everything else on this exact SHA is green: Lint & Static SUCCESS (that lane owns eslint --max-warnings 0, tsc, and the settings-schema freshness check), Integration Tests (no-AK, No Sandbox) SUCCESS, Desktop Shell ubuntu-22.04 and windows-2022 SUCCESS, web-shell E2E Smoke SUCCESS, TUI parity snapshots and OpenTUI no-flicker gate SUCCESS, Secret scan (TruffleHog) SUCCESS, Classify PR SUCCESS. Two of 46 checks are red, and both are analysed here.
No in-scope fix
- Weakening or deleting the guard is off the table. It is a deliberate archive-safety gate whose own comment and ci.yml both state the throw is intentional, and this round has no content evidence that the pinned behaviour is wrong — the behaviour is correct; the runner lacked a binary.
- The real remedy lives in
.github/workflows/ci.yml(make the tooling step able to fail loudly, or pre-landzipthe way ci.yml says tmux is being pre-landed for feat(review): capture-tui — rendering claims get pixels, not prose (Phase 2) #8388)..github/is a hard never-modify area for this loop, and it is outside this PR's one-file footprint, so the footprint gate would reject the expansion as well. - A rerun is the immediate fix, and the repository already owns the mechanism:
qwen-ci-flaky-rerun.yml(Qwen CI Failure Patrol, every 10 minutes,STALE_MINUTES: 30,MAX_CANDIDATES_PER_RUN: 5).
Honest limit on this diagnosis. This environment has no GH_TOKEN/GITHUB_TOKEN, so gh run view --log-failed on run 33849895157 / job 100950304755 is unavailable and I could not read the ::warning:: line directly. The identification rests on three things that do not depend on the log: the red run's duration matching a same-lane green run to within five seconds, the step ordering that puts test:scripts last, and a deterministic local reproduction at HEAD of a failure carrying exactly the required signature. One look at the log settles it — a zip/unzip warning plus the install-script.test.js throw confirms it; a real assertion failure anywhere in the workspaces half refutes it and would be a main-side defect, still not this PR's.
2. Dependency CVE audit — red, and provably not from this PR
What the job does (.github/workflows/security-checks.yml:27-67): npm ci --ignore-scripts --no-audit, then npm audit --omit=dev --audit-level=high on the root workspace, then the same pair for each packages/*/package-lock.json (skipping packages/mobile-mcp, documented as vendored and not installed directly). Hard gate: any high-severity CVE fails it. Its only repository inputs are therefore the three audited lockfiles, the manifests, and .nvmrc.
Those inputs are byte-identical across a red, a green, a red, and current main. Blob hashes at all six relevant SHAs:
6fd9daf07c root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← RED
4076bc7ee5 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← GREEN
043681b8a8 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← RED
e097ba81ba root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← RED (HEAD)
9c320cb0cc root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← merge base (pure main)
80497a74d0 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← current main tip
$ git diff --numstat 9c320cb0cc HEAD -- package.json '**/package.json' package-lock.json '**/package-lock.json' .nvmrc patches
(empty)
The 07:41 base-update merge brought ~100 files into this branch and not one manifest, lockfile, .nvmrc or patch.
Conclusion (proof, not inference): the red verdict is not attributable to this PR, to any commit on this branch, or to anything a code change here could fix. main carries identical inputs, so main's own pushes are exposed to the same verdict.
Mechanism (inference — labelled as such, because I cannot read the job log). Identical inputs producing RED → GREEN → RED means the verdict is driven from outside the tree. Two candidates, both external:
- The advisory data
npm auditqueries moved inside the window. This needs an advisory published, then withdrawn or reclassified, then re-published — possible, but the less likely shape. - The job's own networked steps exited non-zero for a non-CVE reason. The workflow accumulates
npm audit … || status=$?and, per vendored package,( cd … && npm ci … && npm audit … ) || status=$?, thenexit "$status". That treats a registry /EAI_AGAIN/ENOTFOUNDfailure ofnpm ciornpm auditexactly like a real high-severity finding — the gate goes red with no CVE at all. The two vendored lockfiles each need their own coldnpm ci(the job'scache: npmkeys on the root lockfile), on a hosted runner, inside a 15-minute cap; the failing run used 13m03s.
No in-scope fix either way. Clearing a real advisory means changing a dependency version, i.e. editing package.json/package-lock.json. Lockfiles and patches/ are supply-chain areas this round must not touch, and they are outside this PR's footprint (one test file), so the footprint gate would reject the expansion too. The repository already owns the right shape: git log -- package-lock.json surfaces 2a428054c4 chore(deps): bump fast-uri to 3.1.7 to clear the high-severity audit gate (#10862) — a standalone chore(deps) bump against main. If the log instead shows a network failure, the remedy is a rerun.
A trap worth repeating for the shepherd, now with a third data point. The 07:41:36Z base-update (ic:5537341197) saw this check red, verified it green on main, and merged main. The check is red again on the resulting commit. That merge could not have been the remedy: the lockfiles were already byte-identical on both sides, and the merge carried no manifest. Three base-updates (00:54, 04:44, 07:41) have now been spent on red checks that a rerun resolves, not a merge — and the 07:41 one additionally re-exposed this PR to a second runner-side red (§1). Dispatching further base-updates or autofix rounds against these two checks will keep burning budget without moving them.
3. Standing escalation — the PR body claims fixes that already landed on main
Reported as R3-1 and re-confirmed in rounds 4, 5 and now against this SHA. Verified real. It is a defect in the PR body, not in the code, so no code change resolves it — and the change that would resolve it is not one I can make:
- The substance holds at the current tree:
main's69c4f1e4bb— fix(cli): complete the live slash-submit deps and fixture (Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944) (fix(cli): complete the live slash-submit deps and fixture (#10944) #10961) — made both changes this branch had been reduced to (theslashCommandsuseCallbackdependency and the mock'saction: vi.fn()).git diff origin/main...HEADreturns one comment line; that is the whole remaining delta. - address-review mode has no PR-body output.
pr-body.mdis consumed only by the develop-issue publish job (qwen-autofix.yml—gh pr create --body-file), and this round has no GitHub credentials by design. - What remains is a maintainer's choice, unchanged since round 2: correct the body so it describes only the one-line comment, or close the PR as superseded. A third option is now worth naming, because
maincarries the annotated line without the note: land the comment directly againstmainas a one-line follow-up and close this PR — same outcome, no stale body to fix.
Escalated — not declined, not deferred. Declining would be wrong (the finding is real). Deferring to the follow-up queue would also be wrong: a stale PR body does not survive the merge, so there would be nothing left to do by the time a maintainer picked the item up. It needs an answer on this PR.
4. Test Plan path (reported in all six review bodies, marked "not a blocker")
Also a PR-body defect, not a code one. The Test Plan path src/ui/components/InputPrompt.test.tsx is relative to packages/cli; the reviewer resolves it from the repository root, hence no such file or directory. The command that works:
cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx
Re-run this round at e097ba81ba: 215 passed (215), exit 0. Whoever edits the body should make the path repo-root-relative (packages/cli/src/ui/components/InputPrompt.test.tsx) or keep the cd prefix — that also stops this note recurring every round.
5. Reverse audit — the pass the reviewer did not get to run
rv:5110914416 discloses "Not reviewed: reverse audit — stopped before round 1 by the review time budget." That is the reviewer's unfinished work rather than a finding, but it leaves one pass nobody has run on this SHA — and this SHA is new, because the 07:41 merge added 23 lines to InputPrompt.tsx since the last local verification. So I re-checked the diff against the exact code at HEAD rather than trusting earlier rounds:
- The comment's claim is still accurate.
InputPrompt.tsx:1461-1464readsisLiveSlashCommand = commandToExecute?.action !== undefined && args.length === 0 && canonicalPath.length === commandPartCount;, soaction !== undefinedreally is a precondition of the live-slash submit path. - The line it annotates is still load-bearing. Both
/memoryEnter-to-submit tests are present at this SHA —InputPrompt.test.tsx:2800(should submit directly on Enter after arrow-navigate + backspace + retype to perfect match, whose body carries theEnter must submit '/memory', NOT autocomplete 'show'note at :2850) and:2862(should submit directly on Enter for a perfect match without prior arrow navigation) — and the file passes 215/215. - Nothing to remove. No duplicate note elsewhere in
mockSlashCommands, no dead code, no bloat. The neighbouringquitandclearmocks carryaction: vi.fn()unannotated, which is correct rather than inconsistent: only the/memoryEnter path is gate-sensitive, and R1-2 (inline3927468069) asked for the note exactly where it now sits.
6. Why no code change — including the "what can I remove" question
The round's only code-shaped candidate is the one line this PR adds, and removing it would empty the PR entirely. That is not a subtractive cleanup, it is a decision to close — which is §3's open maintainer question, and settling it unilaterally in code is exactly what I should not do. The comment also is not bloat: it is the accepted resolution of reviewer finding R1-2, it states a non-obvious why (an action: vi.fn() on a mock that has subCommands reads as decorative, yet two tests break without it), and §5 re-verified it is factually accurate at this SHA. Everything else in the feedback is either a runner-side red with no reachable fix (§1, §2) or a PR-body defect this mode cannot write (§3, §4).
Verification
Commands actually run this round, on HEAD = e097ba81ba, with no source change before, during or after:
git diff origin/main...HEAD --stat— the PR is 1 file, 1 insertion, 0 deletions:packages/cli/src/ui/components/InputPrompt.test.tsx.git merge-base origin/main HEAD→9c320cb0cc;git rev-parse origin/main→80497a74d0;git rev-parse HEAD→e097ba81ba. HEAD is 2 commits behind main (80497a74d0,05b8ee06a2);--conflict false, so nothing was merged.git diff --numstat 9c320cb0cc HEAD—1 0 packages/cli/src/ui/components/InputPrompt.test.tsx: the entire delta the Test job sees versus a puremaincommit.git diff --numstat 043681b8a8 e097ba81ba— ~100 files, all frommain. The same diff restricted topackage.json/**/package.json/package-lock.json/**/package-lock.json/.nvmrc— empty. Restricted toscripts/and.github/— empty.git rev-parse <sha>:<lockfile>for the three audited lockfiles at6fd9daf07c,4076bc7ee5,043681b8a8,e097ba81ba,9c320cb0cc,80497a74d0— all identical (quoted in §2).git rev-parse <sha>:scripts/tests/install-script.test.jsat043681b8a8,e097ba81ba,9c320cb0cc,80497a74d0— allb033e3d7f9…(quoted in §1).npm run build— passed, exit 0. It also regeneratedpackages/vscode-ide-companion/schemas/settings.schema.jsonbyte-identical to the committed copy;git status --porcelainwas empty afterwards, which is the settings-schema freshness evidence.cd packages/acp-bridge && npx vitest run— passed: Test Files 34 passed (34), Tests 1926 passed (1926), exit 0, 20.85s. First workspace in run order and the largest test delta from the merge.cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx— passed: Test Files 1, Tests 215 passed (215), exit 0, 69.38s. The only file this PR touches.cd packages/cli && npx vitest run src/serve/server.test.ts src/commands/serve.test.ts— passed: Test Files 2 passed (2), Tests 1291 passed (1291), exit 0, 39.91s. The largest merged test surface inpackages/cli.CI=true npm run test:scripts— failed, exit 1, as diagnosed in §1: Test Files 1 failed | 75 passed (76), Tests 2007 passed (2007),FAIL scripts/tests/install-script.test.js — Error: \zip`/`unzip` missing on a CI host; archive tests would skip.This is the reproduction, not a regression: the guard file is byte-identical tomain`'s and to the SHA where this check was green.command -v zip→ absent;command -v unzip→/usr/bin/unzip. The host condition the guard pins.git status --porcelain— empty after every step;git rev-parse HEAD—e097ba81baf29682646ca8f5d9ea4102f258f1c6, unchanged. No commit was created. The test runs'packages/cli/junit.xmlandcoverage/are gitignored, so the tree stayed clean.
Not run, and why:
npm audit --omit=dev --audit-level=high— the failing check's own command. It is a networked package command, which this round is not permitted to run, and it would not change the disposition: the manifests and lockfiles are identical tomain's, so whatever it names is amain-side condition.npm run typecheck,npm run lint— no code change and no commit this round, andLint & Static (ubuntu-latest, Node 22.x)is SUCCESS on this exact SHA; that lane owns eslint with--max-warnings 0,tsc, and the settings-schema freshness check.npm run build(which compiles every package) passed locally as well.npm run generate:settings-schema— no settings source changed in this PR. The merge did bring main'ssettingsSchema.tsand schema edits, but both are already committed onmain, and the build's own schema generation left the tree clean, which is the same freshness evidence.- Integration tests after
npm run bundle— the touched file is a unit-test fixture exercised directly by the focused run above, andIntegration Tests (no-AK, No Sandbox)is SUCCESS on this SHA. - A
zip-present control run oftest:scripts— this sandbox cannot installzip(no apt/network permission), so I could not show the suite green with the binary present. The byte-identity of the guard across the green and red SHAs is the part that actually carries the argument, and that is proven above. - Mutation probe — not applicable: this round adds no guard, branch or behaviour and creates no commit, so there is nothing new to witness. (The
action: vi.fn()witness R1-2 asked for was probed in round 1, and that line now lives onmain.)
What I need from a maintainer
- Rerun
Test (ubuntu-latest, Node 22.x), and if it goes red again read one line of its log. Same guard blob as the SHA where it was green 15 minutes earlier, and a red run whose duration matches that green run to within five seconds — so the suite passed and the step's last command threw. Look for the::warning::tmux/zip install failed…annotation from ci.yml:667 (root branch) or :673 (sudo branch) and theinstall-script.test.jsthrow. If both are there, the durable fix is in.github/workflows/ci.yml(a hard never-modify area for this loop): make the missing-zipcase fail loudly and early, or pre-landzipthe way tmux is being pre-landed for feat(review): capture-tui — rendering claims get pixels, not prose (Phase 2) #8388, instead of letting acontinue-on-errortooling step red a required check after ~15 minutes of green testing. - Rerun
Dependency CVE audit, then read its log once. Same lockfiles asmainand as the run that was green at4076bc7ee5, so the red is external. Ahighseverity table means a real advisory → fix it with achore(deps)bump againstmain(the#10862shape), not inside this PR. A registry/install error means infrastructure → the rerun is the fix. Neither is reachable from this branch. - Stop spending base-updates on these two checks. Three merges of
main(00:54, 04:44, 07:41) have not moved either one, because neither has a tree-side cause; §2 shows the lockfiles were already identical on both sides of every merge. A rerun moves them. The 07:41 merge also re-exposed the PR to a fresh runner-sideTestred. - Decide §3: correct the PR body so it describes only the one-line comment, close the PR as superseded by Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944/fix(cli): complete the live slash-submit deps and fixture (#10944) #10961, or land the comment against
mainas a one-line follow-up and close this. All three are acceptable; I can perform none of them from this round. - Fix the Test Plan path in the body to
packages/cli/src/ui/components/InputPrompt.test.tsx, or keep itscd packages/cliprefix, so the reviewer's check resolves and this note stops recurring in all six review bodies.
中文说明
Autofix 轮次 —— PR #10940(issue #10935):本轮无代码改动
结论:没有提交,工作区也没有任何改动。 git status --porcelain 为空,HEAD 仍是 e097ba81ba。
本轮有两个红色检查、零条审查发现。两个红色都不能归因于本 PR;而且这一次我可以为 Test 的红色指出具体机制——我有了一个同 lane 的绿色参照用来对比时长,并且在本地确定性地复现了该红色所必须具备的那个失败特征。
本轮反馈
| 章节 | 内容 |
|---|---|
| Diff 增长 | 源码净增 0 行 / 测试净增 0 行(预算 400/400,此前 0 轮超预算)—— 仅为信息性 |
## Reviews |
rv:5110914416(COMMENTED,第 6 轮,sha e097ba81ba)。ledger 记录零条发现(findings:[]、posted:0、fresh:0);它只报告了 CI 降级,并披露有一遍审查未完成 |
## Inline comments |
空 |
## Issue-level comments |
空 |
## Failed checks |
Dependency CVE audit: FAILURE、Test (ubuntu-latest Node 22.x): FAILURE —— 唯一可执行条目 |
## Still-red checks |
空 |
没有重试上下文,没有 Deferred non-Critical feedback 章节(因此不是 critical-only 模式),也没有 Growth audit required 章节(因此未产出 growth-audit.json)。--conflict false,所以没有合并任何内容。由于本轮 feedback.md 没有给出任何 rc: 句柄,因此没有可解决或可回复的线程:resolved-comments.txt 与 comment-replies.json 均不产出。
1. Test (ubuntu-latest, Node 22.x) —— 红色,机制已定位,与本 PR 无关
该作业的输入就是 main 的输入
本分支与 origin/main 的 merge base 是 9c320cb0cc,一个纯粹的 main 提交。相对它,本 PR 的全部增量是一行:
$ git diff --numstat 9c320cb0cc HEAD
1 0 packages/cli/src/ui/components/InputPrompt.test.tsx
该行是 InputPrompt.test.tsx:133:
// InputPrompt's live-slash submit gate requires action !== undefined.
一条位于 mockSlashCommands 对象字面量内部的 // 注释。不可执行、不是依赖输入、也不会被任何断言读取。所以 e097ba81ba 上 Test 作业执行的代码与 main@9c320cb0cc 逐字节相同。
时长说明该步骤是在末尾失败的,而不是失败在某个测试上
这是本轮的新证据,也是这次红色与第 4 轮那次的区别所在:
| SHA | 判定 | 时间窗口 | 时长 |
|---|---|---|---|
043681b8a8 |
SUCCESS | 04:44:51 → 05:00:13Z | 15 分 22 秒 |
e097ba81ba |
FAILURE | 07:42:45 → 07:58:02Z | 15 分 17 秒 |
关于出处,因为这两个时间窗口的新鲜度并不相同:红色那次的窗口读自本轮的 checks.json;043681b8a8 上绿色那次的窗口来自第 5 轮的报告,它是在那些检查时间还新鲜时读取的。没有 GH_TOKEN 我现在无法重新读取,所以它是「当时所报」而非「本轮重测」。论证并不依赖那 5 秒——它依赖的是两次运行都落在同一个约 15 分钟的区间内,而第 4 轮那次被争用的 ECS 运行花了 93.9 分钟。
同一 lane、同一 profile,而红色那次消耗的墙钟时间与绿色那次相同——只少了 5 秒。这一点很关键,因为测试步骤是这样构造的(ci.yml:756-760):
npm run test:ci:workspaces -- --retry=2 # RC=$?
if [ "$RC" -eq 0 ]; then
npm run test:scripts -- --retry=2 # 仅当 workspaces 通过时才执行
fi
如果 workspaces 那一半真的有测试失败,npm 会在第一个失败的 workspace 处停止,跳过后面所有 workspace 以及整个 test:scripts——作业会比绿色那次明显更早结束。而这次并没有更早结束。它跑完了全程然后才失败。唯一处于这个位置的命令就是该步骤的最后一条,test:scripts。
(第 4 轮的红色是完全不同的形态:在被争用的 ECS lane 上跑了 93.9 分钟。那个诊断对那次运行是正确的,除了机制恰好相同之外,不能直接套用到这一次。)
在 HEAD 上复现了这个确切的失败
test:scripts 最后一个文件的守卫会在没有 zip 的 CI 主机上于模块作用域抛出。本沙箱正是这样的主机——command -v zip → 不存在,command -v unzip → /usr/bin/unzip。用 CI 的标志位运行 CI 的命令:
$ CI=true npm run test:scripts
FAIL scripts/tests/install-script.test.js [ scripts/tests/install-script.test.js ]
Error: `zip`/`unzip` missing on a CI host; archive tests would skip.
❯ scripts/tests/install-script.test.js:56:9
Test Files 1 failed | 75 passed (76)
Tests 2007 passed (2007)
SCRIPTS_RC=1
注意这个形态:**一个文件失败,而所有实际运行的测试全部通过。**这是收集期抛出,不是断言失败——正是一个步骤在全部真实测试已经绿了之后才失败所需要的特征。scripts/tests/install-script.test.js:49-59 是刻意写成这样的:
// Local minimal images may omit them, but CI must keep the archive-safety
// cases active.
const zipAvailable = process.platform === 'win32' || (spawnSync('zip', ['--version']).error === undefined && spawnSync('unzip', ['-v']).error === undefined);
if (process.env.CI && process.platform !== 'win32' && !zipAvailable) {
throw new Error('`zip`/`unzip` missing on a CI host; archive tests would skip.');
}为什么 runner 可能缺少 zip,以及为什么没有任何标注指出这一点
Install tmux and zip tooling(ci.yml:639-676)是 continue-on-error: true,timeout-minutes: 5,每个 apt-get 调用各自有 140 秒上限。失败时它只发出一条 ::warning::,而该警告自己的文本就说明了后果:"tmux/zip install failed; real-tmux capture tests will be skipped and the zip-packaging suite will throw on CI." ci.yml 还两处记录了这个设计:"The install-script packaging suite needs zip/unzip, and throws on a CI host that ships neither, so a silent skip there is impossible." 所以 runner 上一次 apt 抖动就会让整个测试套件通过之后必需检查变红,而且没有一条失败标注指向它。
绿色与红色之间,守卫代码没有变化
下面是 blob 哈希,不是 diff 摘要:
$ git rev-parse <sha>:scripts/tests/install-script.test.js
043681b8a8 b033e3d7f9… ← Test 绿
e097ba81ba b033e3d7f9… ← Test 红
9c320cb0cc b033e3d7f9… ← merge base(纯 main)
80497a74d0 b033e3d7f9… ← 当前 main tip
而产生这个 SHA 的 07:41 base 更新合并,没有触碰 Test 作业工具链所依赖的任何东西:
$ git diff --numstat 043681b8a8 e097ba81ba -- scripts/ .github/
(空)
同样的守卫、同样的 workflow、同样的 lockfile——唯一可能变化的是那台具体的 runner 有没有 zip。
我排除了「合并进来的 main 代码真的坏了」
07:41 的合并带进来约 100 个 main 的文件,所以「main 弄坏了什么」值得直接测一次,而不是只用时长来论证。在 HEAD 上的定向运行:
packages/acp-bridge—— 运行顺序中的第一个 workspace,也是本次合并中测试增量最大的一个(bridge.test.ts+286、bridge.ts+147):34 个文件 / 1926 个测试通过,退出码 0。packages/cli src/serve/server.test.ts+src/commands/serve.test.ts—— 合并进来的单个最大测试文件(server.test.ts+2413/-163):2 个文件 / 1291 个测试通过,退出码 0。- 本 PR 唯一触碰的文件
packages/cli src/ui/components/InputPrompt.test.tsx:215 通过,退出码 0。 npm run build:退出码 0,并且运行后git status --porcelain为空,说明重新生成的packages/vscode-ide-companion/schemas/settings.schema.json与已提交副本逐字节相同(合并带进来的是 main 的settingsSchema.ts与 schema 改动——两者在 main 上都已提交)。
在这个确切 SHA 上其余检查全绿:Lint & Static SUCCESS(该 lane 负责 eslint --max-warnings 0、tsc 以及 settings schema 新鲜度检查)、Integration Tests (no-AK, No Sandbox) SUCCESS、Desktop Shell ubuntu-22.04 与 windows-2022 SUCCESS、web-shell E2E Smoke SUCCESS、TUI parity snapshots 与 OpenTUI no-flicker gate SUCCESS、Secret scan (TruffleHog) SUCCESS、Classify PR SUCCESS。46 个检查中只有两个是红的,两者都在本文分析过。
没有范围内的修复
- 削弱或删除这个守卫不在考虑之列。它是一道刻意的归档安全门禁,它自己的注释和 ci.yml 都说明这个抛出是有意的,而本轮没有任何内容证据表明它所固化的行为是错的——行为是正确的,是 runner 少了一个二进制。
- 真正的补救位于
.github/workflows/ci.yml(让工具步骤能够大声失败,或者像 ci.yml 所说为 feat(review): capture-tui — rendering claims get pixels, not prose (Phase 2) #8388 预置 tmux 那样预置zip)。.github/对本循环是硬性禁改区域,而且它在本 PR 的单文件 footprint 之外,footprint 门禁同样会拒绝这种扩张。 - 立即的修复就是重跑,而仓库已经有对应机制:
qwen-ci-flaky-rerun.yml(Qwen CI Failure Patrol,每 10 分钟,STALE_MINUTES: 30,MAX_CANDIDATES_PER_RUN: 5)。
**对这个诊断的诚实边界。**本环境没有 GH_TOKEN/GITHUB_TOKEN,所以无法对 run 33849895157 / job 100950304755 执行 gh run view --log-failed,我没能直接读到那条 ::warning::。这个定位依赖三件不依赖日志的事实:红色运行的时长与同 lane 绿色运行相差不超过 5 秒;步骤顺序把 test:scripts 放在最后;以及在 HEAD 上确定性复现出了一个具备所需特征的失败。看一眼日志就能定案——出现 zip/unzip 警告加上 install-script.test.js 的抛出即确认;如果 workspaces 那一半任何地方出现真实断言失败,则推翻此说,但那会是 main 侧的缺陷,仍然与本 PR 无关。
2. Dependency CVE audit —— 红色,且可证明与本 PR 无关
该作业做什么(.github/workflows/security-checks.yml:27-67):先 npm ci --ignore-scripts --no-audit,再对根工作区执行 npm audit --omit=dev --audit-level=high,然后对每个 packages/*/package-lock.json 重复同样两步(跳过 packages/mobile-mcp,workflow 注明它是 vendored、不会被直接安装)。这是硬门禁:任何 high 级别 CVE 都会让它失败。因此它在仓库侧的唯一输入就是三个被审计的 lockfile、各清单文件,以及 .nvmrc。
**这些输入在一次红、一次绿、再一次红,以及当前 main 之间完全逐字节相同。**六个相关 SHA 上的 blob 哈希:
6fd9daf07c root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← 红
4076bc7ee5 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← 绿
043681b8a8 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← 红
e097ba81ba root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← 红(HEAD)
9c320cb0cc root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← merge base(纯 main)
80497a74d0 root=b4d9e7e351… desktop-shell=ccf0961d12… live-host=cfea051227… ← 当前 main tip
$ git diff --numstat 9c320cb0cc HEAD -- package.json '**/package.json' package-lock.json '**/package-lock.json' .nvmrc patches
(空)
07:41 的 base 更新合并给本分支带来了约 100 个文件,其中没有一个是清单文件、lockfile、.nvmrc 或 patch。
**结论(这是证明,不是推断):**红色判定不能归因于本 PR,不能归因于本分支上的任何提交,也不是这里任何代码改动能修复的。main 携带完全相同的输入,所以 main 自己的 push 也暴露在同样的判定之下。
**机制(推断 —— 明确标注为推断,因为我读不到作业日志)。**相同输入产生 红 → 绿 → 红,说明判定由树外因素驱动。两个候选,都是外部的:
npm audit查询的漏洞通告数据在该窗口内发生了变化。这需要一条通告先发布、再被撤回或重新分级、然后再次发布——可能,但形态上更不可能。- 作业自身的联网步骤因非 CVE 原因返回了非零退出码。workflow 用
npm audit … || status=$?累积状态,对每个 vendored 包用( cd … && npm ci … && npm audit … ) || status=$?,最后exit "$status"。这会把npm ci或npm audit的 registry/EAI_AGAIN/ENOTFOUND失败与真实 high 级别发现完全等同处理——门禁在根本没有任何 CVE 的情况下也会变红。两个 vendored lockfile 各自需要一次冷启动npm ci(作业的cache: npm是按根 lockfile 做 key 的),在托管 runner 上、15 分钟上限之内完成;失败的那次运行用了 13 分 03 秒。
**无论哪种情况,都不存在范围内的修复。**清掉一条真实通告意味着改变依赖版本,也就是编辑 package.json/package-lock.json。lockfile 与 patches/ 属于本轮不得触碰的供应链区域,而且它们在本 PR 的 footprint(一个测试文件)之外,footprint 门禁同样会拒绝这种扩张。仓库对这件事已有正确的形态:git log -- package-lock.json 显示出 2a428054c4 chore(deps): bump fast-uri to 3.1.7 to clear the high-severity audit gate (#10862)——一个针对 main 的独立 chore(deps) 版本提升。反之,如果日志显示是网络失败,补救办法就是重跑。
有一个陷阱值得向 shepherd 重复一次,现在有了第三个数据点。07:41:36Z 的 base 更新(ic:5537341197)看到这个检查红色,确认它在 main 上是绿的,于是合并了 main。而在合并产生的提交上,这个检查又红了。合并不可能是补救手段:两侧的 lockfile 本来就已经逐字节相同,而合并没带来任何清单文件。已经有三次 base 更新(00:54、04:44、07:41)花在了靠重跑才能解决、而不是靠合并解决的红色检查上——而 07:41 那次还额外让本 PR 重新暴露在第二个 runner 侧的红色之下(见 §1)。继续围绕这两个检查派发 base 更新或 autofix 轮次,只会持续消耗预算而无法推动它们。
3. 持续上报 —— PR 正文声称的修复已经落到 main
即 R3-1,在第 4、5 轮已被重新确认,本轮再次对照当前 SHA 确认。已核实为真。这是 PR 正文(body)的缺陷,不是代码的缺陷,因此没有代码改动能解决它——而真正能解决它的那个改动我也无法做出:
- 实质内容在当前树上成立:
main的69c4f1e4bb—— fix(cli): complete the live slash-submit deps and fixture (Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944) (fix(cli): complete the live slash-submit deps and fixture (#10944) #10961) —— 做出了本分支此前被缩减后要做的两处改动(slashCommands的useCallback依赖,以及 mock 的action: vi.fn())。git diff origin/main...HEAD只返回一行注释;这就是全部剩余增量。 - address-review 模式没有 PR 正文这一输出。
pr-body.md只被 develop-issue 的发布作业消费(qwen-autofix.yml——gh pr create --body-file),而本轮按设计没有 GitHub 凭据。 - 剩下的是维护者的选择,自第 2 轮以来没有变化:要么修正正文,使其只描述这一行注释;要么以「已被取代」为由关闭 PR。现在值得再提出第三个选项,因为
main上带着被注解的那一行却没有这条注释:把注释作为一行后续改动直接提到main,然后关闭本 PR——结果相同,而且没有陈旧正文需要修。
**上报(escalate)—— 既不驳回,也不转入后续队列。**驳回是错的(发现属实)。转入 follow-up 队列也是错的:陈旧的 PR 正文不会存活到合并之后,等维护者从队列里取出这条时已经无事可做。它需要在本 PR 上得到答复。
4. Test Plan 路径(六份审查正文都提到,标注为「非阻断」)
同样是 PR 正文的缺陷,而非代码缺陷。Test Plan 里的路径 src/ui/components/InputPrompt.test.tsx 是相对于 packages/cli 的,而审查者是从仓库根目录解析的,因此报 no such file or directory。可用的命令是:
cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx
本轮在 e097ba81ba 重新执行:215 passed (215),退出码 0。修正正文的人应把路径改成相对仓库根的 packages/cli/src/ui/components/InputPrompt.test.tsx,或保留 cd 前缀——这也能让这条提示不再每轮重复出现。
5. 反向审计(reverse audit)—— 审查者未能执行的那一遍
rv:5110914416 披露:「Not reviewed: reverse audit — stopped before round 1 by the review time budget.」这是审查者自己未完成的工作,而不是一条发现,但它意味着这个 SHA 上有一遍检查谁都没跑过——而这个 SHA 是新的,因为 07:41 的合并自上次本地验证以来给 InputPrompt.tsx 增加了 23 行。所以我是把 diff 对照 HEAD 的确切代码来核对,而不是相信前几轮的结论:
- 注释的陈述仍然准确。
InputPrompt.tsx:1461-1464是isLiveSlashCommand = commandToExecute?.action !== undefined && args.length === 0 && canonicalPath.length === commandPartCount;,所以action !== undefined确实是实时斜杠提交路径的前置条件。 - **它所注解的那一行仍然是关键依赖。**两个
/memory回车提交用例在此 SHA 上都存在——InputPrompt.test.tsx:2800(should submit directly on Enter after arrow-navigate + backspace + retype to perfect match,其内部在 :2850 带有Enter must submit '/memory', NOT autocomplete 'show'的说明)与:2862(should submit directly on Enter for a perfect match without prior arrow navigation)——且整个文件 215/215 通过。 - 没有可删除的东西。
mockSlashCommands中没有重复的说明注释,没有死代码,没有冗余。相邻的quit与clearmock 带着未加注释的action: vi.fn(),这是正确而非不一致:只有/memory的回车路径对该判定敏感,而 R1-2(inline3927468069)要求把说明正好放在它现在所在的位置。
6. 为什么本轮没有代码改动 —— 包括「我能删掉什么」这个问题
本轮唯一与代码相关的候选就是本 PR 新增的那一行,而删掉它会让 PR 完全变空。那不是减法式的清理,那是「关闭 PR」的决定——也就是 §3 中悬而未决的维护者问题,在代码里单方面把它定下来正是我不该做的事。这条注释也不是冗余:它是审查发现 R1-2 被采纳后的处理结果,它说明了一个不显然的 why(一个带 subCommands 的 mock 上的 action: vi.fn() 看起来像装饰,但缺了它有两个测试会失败),而且 §5 已重新核实它在此 SHA 上事实准确。反馈中其余每一项,要么是够不着修复的 runner 侧红色(§1、§2),要么是本模式无法书写的 PR 正文缺陷(§3、§4)。
验证(Verification)
本轮在 HEAD = e097ba81ba 上实际执行的命令,执行前、执行中、执行后均未改动源码:
git diff origin/main...HEAD --stat—— 本 PR 为 1 个文件、1 处新增、0 处删除:packages/cli/src/ui/components/InputPrompt.test.tsx。git merge-base origin/main HEAD→9c320cb0cc;git rev-parse origin/main→80497a74d0;git rev-parse HEAD→e097ba81ba。HEAD 落后 main 两个提交(80497a74d0、05b8ee06a2);--conflict false,所以没有合并任何内容。git diff --numstat 9c320cb0cc HEAD——1 0 packages/cli/src/ui/components/InputPrompt.test.tsx:相对一个纯main提交,Test 作业所看到的全部增量。git diff --numstat 043681b8a8 e097ba81ba—— 约 100 个文件,全部来自main。同一 diff 限定到package.json/**/package.json/package-lock.json/**/package-lock.json/.nvmrc—— 空。限定到scripts/与.github/—— 空。- 对
6fd9daf07c、4076bc7ee5、043681b8a8、e097ba81ba、9c320cb0cc、80497a74d0执行git rev-parse <sha>:<lockfile>,取三个被审计的 lockfile —— 全部相同(见 §2 引用)。 - 对
043681b8a8、e097ba81ba、9c320cb0cc、80497a74d0执行git rev-parse <sha>:scripts/tests/install-script.test.js—— 全部为b033e3d7f9…(见 §1 引用)。 npm run build—— 通过,退出码 0。它同时重新生成了packages/vscode-ide-companion/schemas/settings.schema.json,与已提交副本逐字节相同;之后git status --porcelain为空,这就是 settings schema 新鲜度的证据。cd packages/acp-bridge && npx vitest run—— 通过:Test Files 34 passed (34),Tests 1926 passed (1926),退出码 0,20.85 秒。这是运行顺序中的第一个 workspace,也是本次合并测试增量最大的一个。cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx—— 通过:Test Files 1,Tests 215 passed (215),退出码 0,69.38 秒。这是本 PR 唯一触碰的文件。cd packages/cli && npx vitest run src/serve/server.test.ts src/commands/serve.test.ts—— 通过:Test Files 2 passed (2),Tests 1291 passed (1291),退出码 0,39.91 秒。这是packages/cli中合并进来的最大测试面。CI=true npm run test:scripts—— 失败,退出码 1,与 §1 的诊断一致:Test Files 1 failed | 75 passed (76),Tests 2007 passed (2007),FAIL scripts/tests/install-script.test.js — Error: \zip`/`unzip` missing on a CI host; archive tests would skip.。这是复现,不是回归:该守卫文件与main` 逐字节相同,也与这个检查曾经变绿的那个 SHA 相同。command -v zip→ 不存在;command -v unzip→/usr/bin/unzip。这正是守卫所固化的主机条件。git status --porcelain—— 每一步之后都为空;git rev-parse HEAD——e097ba81baf29682646ca8f5d9ea4102f258f1c6,未变。**没有创建任何提交。**测试运行产生的packages/cli/junit.xml与coverage/已被 gitignore,因此工作区保持干净。
未执行的命令及原因:
npm audit --omit=dev --audit-level=high—— 失败检查自身的命令。它是联网的包管理命令,本轮不允许执行;而且它不会改变处置结论:清单与 lockfile 与main相同,所以它无论指向什么,都是main侧的状况。npm run typecheck、npm run lint—— 本轮无代码改动、无提交,而Lint & Static (ubuntu-latest, Node 22.x)在这个确切 SHA 上是 SUCCESS;该 lane 负责 eslint(--max-warnings 0)、tsc以及 settings schema 新鲜度检查。本地npm run build(会编译每个包)也已通过。npm run generate:settings-schema—— 本 PR 没有改动 settings 源。合并确实带进了 main 的settingsSchema.ts与 schema 改动,但两者在main上都已提交,而 build 自身的 schema 生成让工作区保持干净,这就是同样的新鲜度证据。npm run bundle之后的集成测试 —— 被触碰的文件是单元测试 fixture,已由上面的定向运行直接覆盖,而Integration Tests (no-AK, No Sandbox)在此 SHA 上是 SUCCESS。- 存在
zip时的test:scripts对照运行 —— 本沙箱无法安装zip(没有 apt/网络权限),所以我无法展示「二进制存在时该套件为绿」。真正支撑论证的是守卫文件在绿色与红色 SHA 之间的逐字节相同,而这一点上面已经证明。 - 变异探测(mutation probe)—— 不适用:本轮没有新增任何守卫、分支或行为,也没有创建提交,因此没有新东西需要见证。(R1-2 要求的
action: vi.fn()见证已在第 1 轮探测过,而那一行现在位于main上。)
我需要维护者做的事
- **重跑
Test (ubuntu-latest, Node 22.x);如果它再次变红,请读一行它的日志。**守卫 blob 与 15 分钟前它还是绿的那个 SHA 相同,而红色运行的时长与那次绿色运行相差不超过 5 秒——所以套件是通过的,是步骤的最后一条命令抛出了。请在日志中找 ci.yml:667(root 分支)或 :673(sudo 分支)那条::warning::tmux/zip install failed…标注和install-script.test.js的抛出。如果两者都在,持久修复位于.github/workflows/ci.yml(对本循环是硬性禁改区域):让缺少zip的情况早期且大声地失败,或者像为 feat(review): capture-tui — rendering claims get pixels, not prose (Phase 2) #8388 预置 tmux 那样预置zip,而不是让一个continue-on-error的工具步骤在约 15 分钟的绿色测试之后把必需检查弄红。 - **重跑
Dependency CVE audit,然后读一次它的日志。**lockfile 与main相同,也与4076bc7ee5那次绿色的运行相同,所以红色来自外部。日志中出现high级别表格,说明是真实通告 → 用针对main的chore(deps)版本提升来修(#10862的形态),而不是在本 PR 内部。出现 registry/安装错误,说明是基础设施问题 → 重跑即是修复。两者都无法从本分支触及。 - **不要再把 base 更新花在这两个检查上。**三次合并
main(00:54、04:44、07:41)都没有推动其中任何一个,因为两者都没有树侧成因;§2 表明每次合并两侧的 lockfile 本来就已经相同。重跑才能推动它们。而 07:41 那次合并还让本 PR 重新暴露在一个新的 runner 侧Test红色之下。 - 对 §3 做决定:修正 PR 正文使其只描述这一行注释;以被 Main CI failed: Qwen Code CI on 678ac2e1ec2d #10944/fix(cli): complete the live slash-submit deps and fixture (#10944) #10961 取代为由关闭 PR;或者把注释作为一行后续改动提到
main然后关闭本 PR。三种都可接受,而本轮我无法执行其中任何一种。 - 修正正文里的 Test Plan 路径为
packages/cli/src/ui/components/InputPrompt.test.tsx,或保留其cd packages/cli前缀,使审查者的检查能够解析,并让这条提示不再在全部六份审查正文中重复出现。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max-2026-09-02
|
🔀 Base updated: red check(s) [Dependency CVE audit, Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Dependency CVE audit, Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No code change this round — PR #10940 (issue #10935)Feedback triaged
Nothing in this feedback names a defect in the code, so there is nothing to reproduce and nothing to fix. The one red check is diagnosed below. The smoke failure is not attributable to this PRWhat this PR actually contains. The whole net diff of head Which step failed. Job Identical-content A/B (the decisive evidence). GitHub compare Two more passes in the same window on other runners:
The newly merged main content is exonerated too. This head merged main and picked up ~7.4k lines of web-shell changes (including The duration is the shape of a retry burn, not a broken assertion. Everything else on this exact head is green: Why I did not change any codeA web-shell change would be (a) outside this PR's footprint — the PR touches only Disclosed gaps
Recommended action (workflow / maintainer, not code)
Test Plan path note (reviewer, non-blocking)The reviewer reported cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsxThis head's green One call left for the maintainer (deliberately not decided here)Main already carries this PR's entire fix: So PR #10940's net diff is now a single explanatory comment above a line main already has. Options: (a) merge it for the comment, or (b) close #10940 as superseded by #10961. That is a scope call for the maintainer — I did not make it, and I did not remove the comment. VerificationCommands actually run this round (no code change, no commit):
中文说明本轮未改动代码 —— PR #10940(issue #10935)已分诊的反馈
这些反馈没有任何一条指出代码中的缺陷,因此没有可复现的问题、也没有可修的东西。唯一的红检查在下方给出诊断。 该 smoke 失败与本 PR 无关本 PR 到底改了什么。 head 失败在哪一步。 job 同内容 A/B 对照(决定性证据)。 GitHub compare 同一时间窗内,另外两个 runner 上也通过:
新合入的 main 内容同样被排除。 本 head 合并了 main,带入约 7.4k 行 web-shell 改动(含 耗时形态像"重试耗尽",不像断言被改坏。 该 head 上其他检查全绿: 为什么没有改任何代码改 web-shell 会(a)超出本 PR 的 footprint —— 本 PR 只碰 已披露的信息缺口
建议的处理(属于工作流 / 维护者,而非代码)
Test Plan 路径说明(审查者提到,非阻断)审查者报告 cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx本 head 上通过的 留给维护者决定的一个问题(此处刻意不做决定)main 已经包含本 PR 的全部修复: 所以 PR #10940 现在的净差异,只是 main 已有的那一行之上的一条解释性注释。可选项:(a) 为了这条注释合并它,或 (b) 以"已被 #10961 取代"为由关闭 #10940。这是维护者的范围判断 —— 我没有替它做决定,也没有删掉这条注释。 验证本轮实际执行的命令(无代码改动、无提交):
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): src/ui/components/InputPrompt.test.tsx — no such file or directory.
中文说明
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No code change this roundRound 9 on PR #10940 (issue #10935). Nothing in this round's feedback warrants a code change: the only review is a zero-finding partial-review disclosure, and the only non-green check is a job that ran past its own 20-minute CI budget in a package this PR does not touch. The branch stays at For orientation: this PR's net diff against [rv:5114885423] COMMENTED (downgraded from Approve: "CI still running") — no code actionThis round's review ledger records zero findings (
So the condition that suppressed the Approve no longer holds; a re-review of the same head can reach a verdict without it. "Not reviewed: reverse audit — stopped before round 1 by the review time budget." A gap in the reviewer's own budget, not a claim about the code. Nothing in this PR's diff (one comment line inside a unit-test mock) interacts with a reverse audit, so it stays open for the next review pass rather than being closed by a code change. "Test Plan (not a blocker):
There is nothing to change in the repository for this: the wording lives in the PR body, which this round cannot edit (the workflow owns every GitHub write). This is the same disclosure as [rv:5105401415] last round, and the answer is unchanged. Failed check:
|
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (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: 86 passed · 0 failed · 86 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:86 通过 · 0 失败 · 86 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10940 deep verification —
|
| # | cell | oracle | result at head | result at control |
|---|---|---|---|---|
| 1 | InputPrompt.tsx base vs head |
sha256 | b25ead7d… |
identical → merge dropped nothing, duplicated nothing |
| 2 | fixture base vs head | git diff HEAD^1..HEAD |
+1 comment line, nothing else |
— |
| 3 | keypress useCallback dep array (66 entries) |
duplicate scan, head & base | 0 duplicates, exactly 1× slashCommands (line 1917) |
base identical (0 dupes, 1×) |
| 4 | gate expression | verbatim text compare | commandToExecute?.action !== undefined && args.length === 0 && canonicalPath.length === commandPartCount |
matches the head commit message byte-for-byte |
| 5 | targeted 3 tests, head as-is | vitest --testNamePattern |
3 passed | 212 skipped (215) |
— |
| 6 | mutant: mock action removed |
vitest | 2 failed | 1 passed — exactly the two named tests, expected "spy" to be called with arguments: [ '/memory', …(1) ] / Number of calls: 0 |
sibling stays green; not a compile break |
| 7 | mutant: comment removed (= base bytes) | vitest | 3 passed | 212 skipped — identical to cell 5 |
mutant proven byte-identical to the base fixture |
| 8 | full suite, head | vitest | 215 passed (215), 0 × lines |
— |
| 9 | full suite, base content (A/A) | vitest | 215 passed (215) |
identical totals, 0 failures on both arms → outcome multiset identical |
| 10 | eslint InputPrompt.tsx --max-warnings 0 |
exit + --format json |
exit 0, 0 unsuppressed warnings (1 pre-existing justified suppression at line 2251, unrelated hook) | planted violation (delete slashCommands from the dep array in a scratch copy) → exit 1, exactly one react-hooks/exhaustive-deps naming slashCommands |
| 11 | navigatedRef autocomplete branch removed | vitest (sibling test) | 1 failed, expected "spy" to be called with arguments: [ +0 ] |
branch is genuinely pinned → Test Plan mechanism confirmed |
Cell 6 is the load-bearing proof: the comment's claim ("the live-slash submit gate requires action !== undefined") is true, and deleting the field it documents reproduces the exact CI failure the body quotes. Cell 7 is the regression proof: the PR's real delta changes nothing observable. Cell 10 proves the lint gate is live rather than vacuously green. Cell 11 settles the Reviewer Test Plan's mechanism sentence by measurement.
Remaining witnesses: 03-sibling-sweep-mocks-and-real-commands.png (the fixture and production censuses behind cells 3 and F2), 04-eslint-live-gate-and-full-suite-aa.png (cells 9–10), 05-testplan-mechanism-navbranch-pin.png (cell 11).
Corrections
These correct the PR description, not the code. The head commit message (6fd9daf0) is accurate throughout; the body was written before the final rebase and never updated.
- The body's two "fixes" are not in this PR's delta. It says commit 1 "un-breaks two unit tests" and commit 2 "un-breaks
Lint & Static". At the base this PR merges into, both are already present and green: the base fixture already carriesaction: vi.fn()(it is a context line in the diff), the base dep array already carries exactly oneslashCommands,eslint --max-warnings 0is clean on the base file (sha256-identical to head), and the base-content suite is 215/215 (cells 8–9). The body's Reviewer Test Plan instructs the reviewer to observe "before the first commit the two/memorysubmit cases fail" — that state does not exist at this head; I could only reach it by mutation (cell 6). - "this is red on
mainitself … blocking every PR branched from it" is false at the current base. Measured on base content: eslint 0 warnings, 215/215. It may have been true at the snapshot'sbaseRefOid(cf44c778…), which is not reachable locally; at the tip this PR actually merges into, it is not. - Confirmed, not corrected: the body's mechanism sentence for the sibling case ("with
navigatedRefset the submit block still routes to the autocomplete branch") holds — cell 11 shows deleting that branch turns the sibling red. One nuance the body does not state: the outcome is over-determined across gate states. In cell 6 the whole submit block is skipped (gate false) and the sibling still passes, via the laterif (showCompletionSuggestions)/ACCEPT_SUGGESTIONpath (line 1525→1545), which produces the same observable (handleAutocomplete(0),onSubmitnot called). So the sibling test pins the branch given the gate is true, not unconditionally.
Findings
F1 — Suggestion: the PR body misdescribes what lands; the effective delta is one comment line
The body presents two functional repairs and a before/after test plan. git diff HEAD^1..HEAD is:
+ // InputPrompt's live-slash submit gate requires action !== undefined.
Reproduce: git diff --stat HEAD^1..HEAD → 1 file changed, 1 insertion(+); git show HEAD^1:packages/cli/src/ui/components/InputPrompt.test.tsx | diff - packages/cli/src/ui/components/InputPrompt.test.tsx → one added line.
Consequence: a maintainer approving this PR believes they are un-blocking two red gates on main; they are merging a comment. The risk is decision-level, not code-level — wrong prioritisation, or a wrong cherry-pick, or leaving the body standing as the record of why the fixture field exists (the head commit message already carries that record correctly).
Suggested action (no code change needed): update the body to the head commit message's account ("the functional fix belongs entirely to main; this branch's remaining delta is that one comment line"), or close as superseded by #10961. The comment itself is worth keeping: it names the hidden constraint that makes a never-invoked, never-asserted vi.fn() load-bearing (cells 4 and 6), which is exactly the sanctioned use of a comment here.
F2 — Nice to have (pre-existing on main, out of this PR's scope): two real container-only commands have no Enter coverage
Census of 67 exported command literals in packages/cli/src/ui/commands (own-level keys only, so a subcommand's action cannot be mistaken for the parent's): 65 have an own action; 2 do not — /agents (agentsCommand.ts) and /arena (arenaCommand.ts), both pure containers. Under the gate the comment documents, typing /agents + Enter makes isLiveSlashCommand false.
No test drives either command through the InputPrompt Enter/submit path: arenaCommand.test.ts / arenaCommand.agentComplete.test.ts exercise the subcommand actions, and opentui/commands-dispatch.test.ts:756 dispatches /arena at the dispatch layer — neither touches the live-slash gate. /agents has no Enter-path test at all.
This is not a dead end: control falls through to the general SUBMIT path at line 1781 (handleSubmitAndClear(buffer.text)), or, with the dropdown open, to the ACCEPT_SUGGESTION path at 1545. Asserted in harness/prod-command-census.mjs (7/7). It is pre-existing behaviour from #10929/#10961; this PR ships no production change, so it neither causes nor fixes it. Noted because it is the one place the gate's real-world consequence is untested, and because the pre-fix mock's action-less shape was actually faithful to these two commands.
Not covered
- Per-commit attribution. The snapshot lists 9 commits; at depth 2 only 1 is locally reachable (
git rev-list HEAD^1..HEAD^2=c447c8c960). The two functional commits (a075310,1e5dd89) were therefore never individually exercised. Mitigated, not eliminated: their content is provably present in the base (cells 1–3, 8–10), which is a stronger statement than per-commit green. - Which PR delivered the two fixes to main. The head commit message names
69c4f1e4bb(fix(cli): complete the live slash-submit deps and fixture (#10944) #10961); that commit is unreachable locally, so the attribution is quoted, not verified. - The original Main CI failed: E2E Tests on b4e9e40bb476 #10935 scenario. Out of scope per the body (closed by fix(cli): submit exact slash commands from the live input #10929); not exercised.
- Repo-wide
typecheck/buildgates. Not run: the delta is a comment inside a test file, and the affected suite plus eslint were run directly. - Dropdown-open Enter path for container commands (F2) was traced by reading lines 1453–1816, not driven end-to-end in a TUI; no E2E/interactive run was performed this round.
- Superseded instruments, disclosed. The first
aa-full-suiterun executed 8 checks of which 1 failed: a per-test-line census that compared--reporter=basicoutput, which prints a per-test line only above a duration threshold (it reported 139 vs 138 lines of unrelated passing tests). That instrument was invalid; it was replaced by a deterministic outcome-count oracle and re-run live (10/0), and the same two saved logs were re-scored (aa-rescore, 11/0). Four other harness bugs were found and fixed mid-round (a regex that matchedlet isLiveSlashCommand = false;and swallowed the block; ANSI bold insideNumber of calls: 0; eslint stderr concatenated onto stdout corrupting the JSON; agit ls-files dir/**/*.tsglob matching zero files). Each initially misreported; only the corrected final runs are counted inassertions.json. Every one of them was caught by a validity control (live-scanner / byte-identity / planted-violation), which is why none became a false finding against the PR. - No GitHub access. Metadata came solely from
$QWEN_VERIFY_CONTEXT; nothing was posted.
Methodology
Environment: the CI verify container (node:22-bookworm, no zstd, no GitHub token), working tree = refs/pull/10940/merge at depth 2, npm ci + npm run build pre-completed at head. Base side used git show HEAD^1:<file> into scratch/ plus byte-level sha256 comparison rather than a second worktree, because the PR touches no dependency manifest and InputPrompt.tsx is sha256-identical base↔head — so the control needed no rebuild and no workspace-link re-pointing (the one internal link, node_modules/@qwen-code/qwen-code-core, resolves into the head tree, which the PR never modifies). Behavioural arms ran the real compiled-through-vitest source with real ink rendering (npx vitest run src/ui/components/InputPrompt.test.tsx), never a stub of the code under test; mutants were applied to a byte backup and restored in a finally block, with the restore asserted by sha256 (final tree: git status --porcelain empty; fixture e4a57724…, source b25ead7d…). Harnesses live in harness/ (ab-fixture.mjs, aa-full-suite.mjs, aa-rescore.mjs, census.mjs, dep-array.mjs, gate-text.mjs, lint-live-gate.mjs, navbranch-probe.mjs, prod-command-census.mjs); raw per-arm logs in logs/; the five PNG witnesses in evidence/, each produced by one node scripts/verify-capture.mjs invocation over a live re-run of the harness it pictures. Assertion counts are the ASSERTIONS pass=N fail=M lines of each harness's final run, tallied in logs/.
Assertion breakdown (final runs)
| harness | pass | fail |
|---|---|---|
dep-array.mjs @ head |
6 | 0 |
dep-array.mjs @ base |
6 | 0 |
gate-text.mjs @ head |
8 | 0 |
census.mjs @ head |
3 | 0 |
census.mjs @ base |
3 | 0 |
ab-fixture.mjs |
15 | 0 |
lint-live-gate.mjs |
11 | 0 |
navbranch-probe.mjs |
6 | 0 |
aa-full-suite.mjs (corrected re-run) |
10 | 0 |
prod-command-census.mjs |
7 | 0 |
aa-rescore.mjs (re-score of first A/A logs) |
11 | 0 |
| total | 86 | 0 |
Flakiness gate log
rounds=5 files=1 skipped=0
file packages/cli/src/ui/components/InputPrompt.test.tsx: (cd packages/cli) npx --no-install vitest run ./src/ui/components/InputPrompt.test.tsx
per-file results (P=pass F=fail I=infra-exit, one letter per run):
packages/cli/src/ui/components/InputPrompt.test.tsx: PPPPP
verdict: pass
summary: 1 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/ui/components/InputPrompt.test.tsx: P (exit 0)
round 2 · packages/cli/src/ui/components/InputPrompt.test.tsx: P (exit 0)
round 3 · packages/cli/src/ui/components/InputPrompt.test.tsx: P (exit 0)
round 4 · packages/cli/src/ui/components/InputPrompt.test.tsx: P (exit 0)
round 5 · packages/cli/src/ui/components/InputPrompt.test.tsx: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qqqys
left a comment
There was a problem hiding this comment.
APPROVE (verified at head c447c8c)
- Historical blocker cleared by scope reduction, not by argument. The round-1 CHANGES_REQUESTED said the linked issue #10935 was already fixed by #10929 (merged minutes before this PR opened) and the branch was CONFLICTING against the winner. The author dropped the duplicated production change (confirmed in-thread by yiliang114) and the PR's whole net diff against current main is now one explanatory comment line on
mockSlashCommands.memory's load-bearingaction: vi.fn()inInputPrompt.test.tsx— the fixture repair that outlived #10929. GitHub reportsMERGEABLEat this head; the racing duplicate is gone from the diff. - The comment itself is exactly what round-1's R1-2 asked for: the stub is never invoked or asserted, yet the live-slash submit gate (
commandToExecute?.action !== undefinedinInputPrompt.tsx) silently requires its existence, so the line prevents a future fixture tidy from breaking the two repaired submit tests with an unhelpfulNumber of calls: 0. R1-1 (pin the staleness race by test) was declined with a structural reachability argument (exportCompletionshares theslashCommandsdependency chain, so the stale-closure state cannot exist in isolation) — S-tier either way, not gating. - No Critical is possible in a one-line test comment diff; the surrounding fixture and gate match the description at head.
- CI at head: 19 green, one run still in progress (non-gating), and the lone completed non-green is
web-shell E2E Smoke | cancelled— the fleet-side cancellation seen across unrelated PRs this week, with no contact between this cli test file and that lane.
The human maintainer independently approved this head before this review.
|
Thanks for the PR! Template looks good ✓ Problem: this was an observed bug, not theoretical hardening — two deterministic What survives that collapse is the entirety of this PR's diff — one line: + // InputPrompt's live-slash submit gate requires action !== undefined.Direction: unblocking Size: not applicable. The only changed file is Approach: the surviving line is defensible and I'd keep it — the reasoning is in the code review below. The framing is the issue, not the content. The title still reads Risk: no elevated risk signals. The single changed file is a Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:这是一个已观测到的 bug,不是理论性加固——两个确定性失败的 合并坍缩之后剩下的,就是本 PR diff 的全部内容——一行: + // InputPrompt's live-slash submit gate requires action !== undefined.方向:解堵 规模:不适用。唯一改动的文件是 方案:留下的这一行是站得住脚的,我倾向于保留——理由见下方代码审查。问题出在表述,不在内容。标题仍是 风险:无升级风险信号。唯一改动的文件是 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewMy independent proposal from the title and "Why it's needed" alone: add The one surviving line is correct, and I checked the claim rather than trusting it. The comment asserts the live-slash submit gate requires It also earns its place under the house comment policy. The mock No critical findings. No reuse, duplication, abstraction, or package-boundary concerns — it is a comment. One non-blocking hygiene point, carried over from the gate: the title and body still describe the two scooped fixes as this PR's content. Merging as-is puts Test evidence — the PR's own CIUnattended CI run, so per the gate rules I did not build, run, or check out any PR-derived code; everything below is read from the GitHub API for the reviewed commit.
The two checks that both of the originally-described fixes turned on are green on this commit: The Not verified: the root cause of that cancellation — the job log is not retrievable through the API for a cancelled run, so I have the timing but not a stack. What that leaves is a missing signal, not a suspicious one: On the sandboxed lanes: I am deliberately not naming Real-scenario testing: N/A — unattended CI run (no tmux; PR code is never executed here), and independently N/A on the merits, since a test-fixture comment changes nothing a user can observe. 中文说明代码审查只读标题和"为什么需要"时,我自己的独立方案是:给 mock 留下的这一行是准确的,而且我核对了它的断言而不是直接采信。 注释声称实时斜杠提交判定要求 按本仓库的注释规范,这一行也确实该有。mock 的 无严重问题。复用、重复、抽象、包边界方面均无异议——毕竟只是一行注释。 一条非阻断的规范性提醒(承接 gate 部分): 标题和正文仍在把已被抢先的两处修复描述为本 PR 的内容。按现状合并,会把 测试证据 —— PR 自身的 CI本次为无人值守 CI 运行,因此按 gate 规则我没有构建、运行或 checkout 任何 PR 派生代码;以下内容全部通过 GitHub API 针对被审提交读取。 CI 表格见上方英文部分(由 finalize 流程就地更新,不在此重复)。 两个与最初描述的两处修复直接相关的检查,在该提交上都是绿的:
未验证:该取消的根因——被取消的 run 其 job 日志无法通过 API 取得,所以我只有时间点,没有堆栈。这留下的是信号缺失,而不是可疑信号: 关于沙箱验证通道:我刻意不在此点名 真实场景测试:N/A —— 本次为无人值守 CI 运行(不使用 tmux,且在此绝不执行 PR 代码);并且就其本身而言也是 N/A,因为测试 fixture 里的注释不会改变任何用户可观察到的东西。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — the surviving one-line delta is accurate, zero-risk, and worth keeping; the one real defect is that the title and body still describe work this PR no longer contains. Stepping back: this PR was scooped, and it was scooped by a fix that is character-for-character the same as its own. It made both edits at 16:37 and 16:51 UTC on 2026-09-03; #10961 merged the identical pair at 22:37; the subsequent It is. I verified the claim against So the honest answer to "would I curse this in six months" is: I'd thank whoever wrote the comment, and curse whoever merged it under a title claiming it repaired One process observation for whoever owns the autofix loop, offered as a signal and not as a finding against this PR: after #10961 landed at 22:37 on 09-03 this branch had nothing left to contribute, yet it went on to accumulate six more Addendum — recorded after the fact, because the ordering matters. @wenshao merged this PR at 04:13:42 UTC as What landed on 中文说明信心度:4/5 —— 存活下来的那一行增量准确、零风险、值得保留;唯一实际的问题是标题与正文仍在描述本 PR 已不再包含的工作。 退一步看:这个 PR 被抢先了,而且抢先它的那份修复与它自己的改动逐字符相同。它在 2026-09-03 16:37 与 16:51 UTC 完成了两处改动;#10961 于 22:37 合入了完全相同的一对;随后的 值得。我对照 所以对"六个月后我会不会骂人"的诚实回答是:我会感谢写下这条注释的人,也会骂那个用一个声称修复了 给 autofix 循环负责人的一条流程观察,作为信号提出,而非针对本 PR 的问题认定:在 #10961 于 09-03 22:37 合入之后,这个分支已经没有任何可贡献的内容,却仍在随后约 40 小时里累积了 6 个 补记 —— 事后记录,因为先后顺序很关键。 @wenshao 已于 04:13:42 UTC 将本 PR 合并为 落到 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅ One non-blocking note in my Stage 3 comment: the title and body still describe the two fixes that #10961 landed, so a retitle to match the surviving one-line delta would keep history honest.
Maintainer verification — built a real environment and ran the PR's own gatesI built a dedicated worktree at current Headline: both defects this PR describes are real and I reproduced them exactly — but both were already fixed on 1. Provenance — the substance already landed via a sibling PR
2. Four-arm A/B on a real worktreeWorktree
Arm 2 reproduces exactly the two tests the description names, and no others — the sibling case Arm 4 is byte-identical to arm 1 — the remaining diff changes no observable behaviour. 3. Is the comment itself accurate?Yes. One nuance the comment does not capture: the 4. Why the comment still has valueCounterfactual A is the argument for merging it. Deleting I also audited the rest of the fixture: every other mock command in RecommendationNon-blocking; merge or close, both defensible — but the PR description must be corrected before merging.
Suggested actions, in order of preference:
Either way this should not merge with the current description attached. Verification environment (reproducible)Node 22, Linux. Test runs were done with coverage disabled after an overlapping-run coverage temp-dir collision; the final numbers come from clean single runs. 中文版维护者验证 —— 搭建真实环境并跑通 PR 自己指定的验收命令我在当前 结论:本 PR 描述的两个缺陷都是真实的,我也精确复现了它们 —— 但在本分支 rebase 之前,它们就已经在 1. 溯源 —— 实质改动已由兄弟 PR 合入
2. 真实 worktree 上的四臂 A/Bworktree
实验臂 2 精确复现了描述点名的那两个用例,且不多不少 —— 相邻用例 实验臂 4 与实验臂 1 完全一致 —— 剩余 diff 不改变任何可观测行为。 3. 这行注释本身准确吗?准确。 注释未覆盖的一个细节: 4. 为什么这行注释仍然有价值反事实 A 就是合入它的理由。删掉 我还审计了 fixture 的其余部分: 建议非阻塞;合入或关闭都说得通 —— 但合入前必须先修正 PR 描述。
建议动作,按优先级:
无论选哪条,都不应带着当前描述合入。 验证环境(可复现)Node 22,Linux。在一次并发运行导致 coverage 临时目录冲突后,测试改为关闭 coverage 运行;最终数字取自干净的单次运行。 |








What this PR does
Repairs two pieces of fallout on
mainfrom #10929's live slash submission gate, in two commits:memoryslash command inInputPrompt.test.tsxanaction, matching the realmemoryCommand— this un-breaks two unit tests.slashCommandsentry to the keypressuseCallbackdependency array inInputPrompt.tsx— this un-breaksLint & Staticunder--max-warnings 0.This PR previously carried an alternative fix for #10935. #10929 landed first and closed that issue, so the branch was rebased onto
mainand reduced to the two genuinely remaining items above.Why it's needed
#10929 changed the Enter path in
InputPrompt.tsxso slash-led input no longer trusts the render-publishedcompletion.isPerfectMatch. At keypress time it runsparseSlashCommand(buffer.text, slashCommands)and only treats the input as a finished command whencommandToExecute?.action !== undefined, there are no leftover args, and the canonical path covers every typed part. Two things broke onmainas a result:memorycommand inInputPrompt.test.tsxwas a pure container with onlysubCommandsand noaction, so/memory+ Enter no longer submits under that gate. Two existing tests fail deterministically (also with--retry=2):should submit directly on Enter after arrow-navigate + backspace + retype to perfect matchandshould submit directly on Enter for a perfect match without prior arrow navigation, both withexpected "spy" to be called with arguments: [ '/memory', …(1) ]andNumber of calls: 0(run 33774521692). The realmemoryCommandhas anaction(it opens the memory dialog), so production behaviour is correct — only the mock drifted.useCallbacknow readsslashCommandsbut never listed it in its dependency array, soreact-hooks/exhaustive-depsemits one warning andLint & Staticfails under--max-warnings 0— this is red onmainitself (run 33776698676), blocking every PR branched from it.Reviewer Test Plan
How to verify
In
packages/cli, runnpx vitest run src/ui/components/InputPrompt.test.tsx: before the first commit the two/memorysubmit cases fail with 0onSubmitcalls, after it they pass. The sibling caseshould autocomplete on Enter when user arrow-navigated a perfect-match suggestion listkeeps passing because withnavigatedRefset the submit block still routes to the autocomplete branch. For the second commit,npx eslint packages/cli/src/ui/components/InputPrompt.tsx --max-warnings 0is clean (previously onereact-hooks/exhaustive-depswarning).Evidence (Before & After)
N/A — test fixture plus hook dependency array; no user-visible behaviour.
Tested on
Environment (optional)
Unit tests only:
npm run buildonce in a fresh worktree, thennpx vitest run src/ui/components/InputPrompt.test.tsxinpackages/cli; eslint run from the repo root.Risk & Scope
slashCommandsprop identity changes, which also fixes a potential stale-closure read of the command list.Linked Issues
References #10929 (the change that surfaced both issues). No open issue — both failures were observed directly on
mainCI.中文说明
这个 PR 做了什么
修复 #10929 的实时斜杠提交判定在
main上留下的两处后遗症,共两个提交:InputPrompt.test.tsx里 mock 的memory斜杠命令补上action,与真实的memoryCommand保持一致——修复两个单元测试。InputPrompt.tsx按键处理的useCallback依赖数组补上缺失的slashCommands——修复--max-warnings 0下失败的Lint & Static。本 PR 原本承载的是 #10935 的另一种修法,但 #10929 先合入并关闭了该 issue,所以分支已 rebase 到
main,收窄为上面这两个真正剩余的问题。为什么需要
#10929 修改了
InputPrompt.tsx的 Enter 路径:/开头的输入不再信任渲染发布的completion.isPerfectMatch,而是在按键时用parseSlashCommand(buffer.text, slashCommands)实时解析,只有当commandToExecute?.action !== undefined、没有多余参数、且 canonical path 覆盖所有输入片段时才视为完整命令。这导致main上出现两个问题:InputPrompt.test.tsx里 mock 的memory命令是只有subCommands、没有action的纯容器,于是该判定下/memory+ Enter 不再提交。两个已有用例确定性失败(--retry=2重试后依然失败):should submit directly on Enter after arrow-navigate + backspace + retype to perfect match和should submit directly on Enter for a perfect match without prior arrow navigation,都报expected "spy" to be called with arguments: [ '/memory', …(1) ]、Number of calls: 0(见 run 33774521692)。真实的memoryCommand是有action的(打开 memory 对话框),所以生产行为没问题——只是 mock 没跟上。useCallback现在读取了slashCommands,但依赖数组里没有列出它,于是react-hooks/exhaustive-deps产生一条警告,在--max-warnings 0下导致Lint & Static失败——main本身就是红的(run 33776698676),卡住所有从 main 拉出的 PR。评审测试计划
如何验证
在
packages/cli运行npx vitest run src/ui/components/InputPrompt.test.tsx:第一个提交前,两个/memory提交用例因onSubmit0 次调用而失败;提交后通过。相邻用例should autocomplete on Enter when user arrow-navigated a perfect-match suggestion list依然通过,因为navigatedRef已置位时提交块仍走自动补全分支。第二个提交可用npx eslint packages/cli/src/ui/components/InputPrompt.tsx --max-warnings 0验证,结果干净(此前有一条react-hooks/exhaustive-deps警告)。前后证据
N/A —— 测试 fixture 加 hook 依赖数组改动,无用户可见行为变化。
测试环境
环境(可选)
仅单元测试:新 worktree 中先执行一次
npm run build,然后在packages/cli运行npx vitest run src/ui/components/InputPrompt.test.tsx;eslint 在仓库根目录运行。风险与范围
slashCommandsprop 标识变化时重建,这同时也修复了对命令列表的潜在过期闭包读取。关联 Issue
引用 #10929(暴露这两个问题的改动)。没有未关闭的 issue——两个失败都直接观察自
main的 CI。