fix(cli): complete the live slash-submit deps and fixture (#10944) - #10961
Conversation
Main's post-merge lane is red on its ESLint step, and two unit tests in the same area fail next to it. Both trace to #10929, which made Enter resolve a `/`-prefixed buffer against the live command list instead of the render-derived completion that can still describe the previous keystroke. The keypress callback that reads that list never added it to its dependencies, so `react-hooks/exhaustive-deps` reports a missing `slashCommands` and the lane's `--max-warnings 0` turns the single warning into a failed step. The omission is also the very staleness the commit set out to remove: a handler captured against an older list resolves the live command from stale data. The same commit exposed a fixture that never matched production. The test command list declared `memory` with subcommands but no action, while the real command is runnable, and the hook both the old and the new path consult requires an action before it calls a match perfect. Two tests therefore mocked a perfect match for a buffer the live list called unrunnable — a state the real hook cannot produce — and Enter stopped submitting. Giving the fixture the action the real command has restores both with their assertions untouched; a third test sharing the fixture had been passing only because the perfect-match branch was dead for that buffer, and now exercises the path it was written for.
Autofix report — issue #10944 (
|
| Job | Conclusion | Window |
|---|---|---|
| Classify PR | success | 15:57:42 → 15:57:46 |
| Test (ubuntu-latest, Node 22.x) | success | 15:57:49 → 16:29:15 |
| Lint & Static (ubuntu-latest, Node 22.x) | failure | 15:57:50 → 16:34:58 |
Inside Lint & Static (self-hosted runner ecs-qwen-hk3-3), steps 1–21 are all success — including Install dependencies (15:58:15 → 16:19:23) and Install linters. Step 22 Run ESLint started at 16:19:50 and recorded no conclusion and no completion time; steps 23–34 never started. A step that starts and never concludes, with the job then failing roughly 15 minutes later, is a runner that stopped reporting mid-step — not an assertion that failed.
That also explains the issue's wording. Lint & Static runs ESLint, actionlint, shellcheck, yamllint, Prettier, the generated-artifact freshness gates and node --test helper suites; none of them print the vitest FAIL / pytest FAILED lines the filer dedupes on, so this lane can only ever produce a per-commit issue.
The commit under test changed two timeout-minutes values plus their comments in npm-cache.yml and sdk-java.yml. Neither file is read by ci.yml, and every repository-side check that lane runs was re-executed locally against this tree and passed (listed under Verification). There is no content in that commit that can fail this lane, and a runner that dies mid-step is not repairable from the repository.
The reproducible defect in the same lane and the same step
The next post-merge run, 33776698676 at commit 661f41eef0 (current main), fails the same job on the same step, this time with a recorded conclusion:
Lint & Static→ step 22Run ESLint=failure(16:39:11 → 16:41:01)
Reproduced locally with the exact command that step runs (node scripts/lint.js --eslint → npm run lint:ci):
packages/cli/src/ui/components/InputPrompt.tsx
1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands'.
Either include it or remove the dependency array react-hooks/exhaustive-deps
✖ 1 problem (0 errors, 1 warning)
ESLint found too many warnings (maximum: 0). → exit 1
Two unit tests in the same area fail on the merged tree as well, which is the Test lane's business:
FAIL src/ui/components/InputPrompt.test.tsx > InputPrompt > should submit directly on Enter
after arrow-navigate + backspace + retype to perfect match
FAIL src/ui/components/InputPrompt.test.tsx > InputPrompt > should submit directly on Enter
for a perfect match without prior arrow navigation
AssertionError: expected "spy" to be called with arguments: [ '/memory', …(1) ]
Number of calls: 0
Root cause
Both come from #10929, which made Enter resolve a /-prefixed buffer against the live command list instead of the render-derived completion, because that completion can still describe the previous keystroke when Enter arrives.
-
The lint gate. The keypress callback that now reads the command list never added it to its own dependency array.
react-hooks/exhaustive-depsreports the missingslashCommands, and this lane runs ESLint with--max-warnings 0, so one warning is a failed step. The omission is not only a lint problem: a handler captured against an older list resolves the live command from stale data, which is precisely the staleness fix(cli): submit exact slash commands from the live input #10929 set out to remove. -
The two tests. The new path decides a perfect match by requiring the resolved command to have an
action. That mirrors production exactly —usePerfectMatchinuseSlashCompletion.tsrequiresleafCommand.actiontoo, andisPerfectSlashMatchin the OpenTUI port is documented as a port of it. The test fixture, however, declaredmemorywithsubCommandsand noaction, while the realmemoryCommandis runnable (action: async () => ({ type: 'dialog', dialog: 'memory' })). So both tests mockedisPerfectMatch: truefor a buffer whose live command list said "not runnable" — a state the real hook cannot produce. While the submit path trusted the mock the discrepancy was invisible; once it consulted the fixture, Enter stopped submitting.
The fix (two lines)
packages/cli/src/ui/components/InputPrompt.tsx: addslashCommandsto thehandleKeypressdependency array, next to thebufferandcompletionthe same branch reads.packages/cli/src/ui/components/InputPrompt.test.tsx: give the fixture'smemorycommand theactionthe real command has.
No assertion was changed, weakened, or deleted. A third test sharing that fixture — should autocomplete on Enter when user arrow-navigated a perfect-match suggestion list — had been passing only because the perfect-match branch was dead for /memory; with the fixture corrected it exercises the path it was written for and still passes.
Mutation probes (witness for each added line)
Both changes were probed in the failing direction on the real tree, not inferred:
- Probe A — dependency entry. With
slashCommandsabsent from the array (the tree as merged),npm run lint:ciexits 1 with1892:5 warning … missing dependency: 'slashCommands'. With it present,npm run lint:ciexits 0. Observed both ways. - Probe B — fixture
action. Withaction: vi.fn()removed from the fixture'smemoryentry, the two named tests fail withNumber of calls: 0ononSubmit. With it present, the file runs 215/215. Observed both ways on an otherwise byte-identical tree (git diffempty at the revert point).
Reported, deliberately not changed
- The
678ac2e1ecrunner death itself. Infrastructure: a self-hosted runner stopped reporting insideRun ESLint. There is no repository-side defect to repair and no evidence supporting a workflow change, so none was made. This is the same shared-pool contention that ci: size two job caps for a contended install, not a quiet one #10931 (the commit under test) was sizing caps against;ci.yml's two lanes already carry 120/90-minute ECS caps and this job died at 37 minutes, well inside them. - 44 failures across 12 files when running the whole
packages/clisuite in this sandbox are environmental, not regressions. They are byte-for-byte identical with the fix reverted (same 12 files, same per-file counts:llm.test.tsx21,config.test.ts5,Footer.test.tsx4, and 9 files with 1–3 each). The causes are visible in the output:Error: EACCES: permission denied, mkdir '/home/github-runner/.qwen', and sandbox-image assertions defeated by theSANDBOX/QWEN_CODE_CUSTOM_SANDBOX_IMAGEvariables this agent's own container injects. CI does not set those. - Two false greens in this lane, outside this issue's scope and outside the areas this bot may modify.
node scripts/lint.js --sensitive-keywords(theRun sensitive keyword linterstep) has no matching branch inscripts/lint.js'smain(), so it exits 0 without linting anything; and--prettierrunsprettier --write ., which rewrites the runner's checkout and cannot fail on unformatted input. Both make the lane greener than it looks. Flagging rather than fixing: changing either could turnmainred on violations nobody has been measuring, and both live in CI machinery.
Verification
Commands actually run in this checkout, on the committed tree unless stated:
npm run build— passed (exit 0)npm run typecheck— passed (exit 0)npm run lint— passed (exit 0)npm run lint:ci(the exact command the failing CI step runs) — passed (exit 0). Before the fix: exit 1 with the warning quoted above.npx vitest run src/ui/components/InputPrompt.test.tsx src/ui/components/InputPrompt.suggestionMouse.test.tsxinpackages/cli— 226 passed, 0 failed (2 files). Before the fix the first file ran 213 passed / 2 failed.- Whole
packages/clisuite (npx vitest run) — 28095 passed, 44 failed; the identical 44 fail with the fix reverted, and are sandbox artifacts as documented above. Reported for honesty, not as a pass. node --test --test-concurrency=1 $HELPER_TESTS(all 23 helper suites) — 511 passed, 0 failed (run while diagnosing; untouched by this change).- Also run while diagnosing, all passing:
bash .github/scripts/check-workflow-size.sh(with the push lane's base SHA),npm run audit:runtime:critical,npm run check:lockfile,npm run check:desktop-isolation,npm run check:tui-dep-direction,npm run check-i18n,npm run generate:settings-schemaandnpm run generate:notices --workspace=qwen-code-vscode-ide-companion(both left the tree clean, so both freshness gates hold), andnpx prettier --checkon the two workflow files the issue's commit touched. npm run generate:settings-schema— not required (no settings source changed), but run anyway as above;packages/vscode-ide-companion/schemas/settings.schema.jsonstayed clean, so no regenerated artifact needed committing.- Integration tests after
npm run bundle— not run. The touched behavior is unit-level React keypress handling and a test fixture, both exercised directly by the suites above; nothing here is reachable only through the bundled CLI or the integration harness. actionlint/yamllint/shellcheck— not run: none of the three binaries is installed in this container and there is nopip3to install yamllint. They are unaffected by this change, which touches only a.tsxdependency array and a test fixture.
中文说明
自动修复报告 —— issue #10944(Main CI failed: Qwen Code CI on 678ac2e1ec2d)
该 issue 报告了什么
Issue #10944 跟踪的是 commit 678ac2e1ec2d 上的 main 分支运行 33773880468(工作流 Qwen Code CI)。它以「按 commit 归档」的标记创建,原因是「一次 main 分支 CI 运行在报告任何测试结果之前就失败了」。
这次运行实际发生了什么
以下信息来自该运行自身的 job 元数据,通过公开的、无需认证的 REST API 读取(只读;本 agent 不持有任何 GitHub 凭据,也没有执行任何写操作)。没有认证就无法下载 job 日志,因此步骤时间线就是本次的证据:
| Job | 结论 | 时间窗口 |
|---|---|---|
| Classify PR | success | 15:57:42 → 15:57:46 |
| Test (ubuntu-latest, Node 22.x) | success | 15:57:49 → 16:29:15 |
| Lint & Static (ubuntu-latest, Node 22.x) | failure | 15:57:50 → 16:34:58 |
在 Lint & Static 内部(自托管 runner ecs-qwen-hk3-3),第 1–21 步全部为 success,包括 Install dependencies(15:58:15 → 16:19:23)和 Install linters。第 22 步 Run ESLint 于 16:19:50 开始,但既没有记录结论、也没有记录完成时间;第 23–34 步从未开始。一个已经开始却永远没有结论的步骤、随后 job 在约 15 分钟后失败,这是 runner 在步骤中途停止上报的特征 —— 而不是某个断言失败。
这也解释了 issue 的措辞。Lint & Static 运行的是 ESLint、actionlint、shellcheck、yamllint、Prettier、生成产物的新鲜度校验以及 node --test 辅助测试套件;它们都不会打印归档脚本用于去重的 vitest FAIL / pytest FAILED 行,所以这条通道只可能产出「按 commit 归档」的 issue。
被测试的那个 commit 只修改了 npm-cache.yml 和 sdk-java.yml 中的两个 timeout-minutes 取值及其注释。ci.yml 并不读取这两个文件,而该通道运行的每一项仓库侧检查都已在本地针对当前代码树重新执行并通过(见「验证」一节)。那个 commit 的内容中没有任何东西能让这条通道失败,而一个在步骤中途死掉的 runner 也无法从仓库侧修复。
同一通道、同一步骤上可复现的缺陷
下一次合并后运行 33776698676(commit 661f41eef0,即当前 main)在同一个 job 的同一个步骤上失败,而且这次记录了明确的结论:
Lint & Static→ 第 22 步Run ESLint=failure(16:39:11 → 16:41:01)
用该步骤实际执行的命令(node scripts/lint.js --eslint → npm run lint:ci)在本地复现:
packages/cli/src/ui/components/InputPrompt.tsx
1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands'.
Either include it or remove the dependency array react-hooks/exhaustive-deps
✖ 1 problem (0 errors, 1 warning)
ESLint found too many warnings (maximum: 0). → exit 1
同一区域还有两个单元测试在合并后的代码树上失败,那属于 Test 通道的范围:
FAIL src/ui/components/InputPrompt.test.tsx > InputPrompt > should submit directly on Enter
after arrow-navigate + backspace + retype to perfect match
FAIL src/ui/components/InputPrompt.test.tsx > InputPrompt > should submit directly on Enter
for a perfect match without prior arrow navigation
AssertionError: expected "spy" to be called with arguments: [ '/memory', …(1) ]
Number of calls: 0
根因
两者都源自 #10929。该改动让 Enter 键针对以 / 开头的输入缓冲区,改为对照「实时命令列表」解析,而不再依赖由渲染派生的补全状态 —— 因为 Enter 到达时,后者可能仍在描述上一次按键。
-
Lint 关卡。 那个现在会读取命令列表的按键回调,从未把它加进自己的依赖数组。
react-hooks/exhaustive-deps因此报告缺失slashCommands,而这条通道以--max-warnings 0运行 ESLint,所以一条 warning 就等于步骤失败。这不仅仅是 lint 问题:一个捕获了旧列表的处理函数会用过期数据去解析实时命令,而这恰恰是 fix(cli): submit exact slash commands from the live input #10929 想要消除的那种陈旧状态。 -
那两个测试。 新路径判定「完全匹配」的方式是要求解析到的命令带有
action。这与生产代码完全一致 ——useSlashCompletion.ts中的usePerfectMatch同样要求leafCommand.action,而 OpenTUI 移植版中的isPerfectSlashMatch在文档里就写明是它的移植。但测试夹具把memory声明为只有subCommands、没有action,而真实的memoryCommand是可执行的(action: async () => ({ type: 'dialog', dialog: 'memory' }))。于是这两个测试为一个「实时命令列表判定为不可执行」的缓冲区 mock 出了isPerfectMatch: true—— 这是真实 hook 不可能产生的状态。当提交路径还信任 mock 时,这个不一致是隐形的;一旦它改为查询夹具,Enter 就不再提交了。
修复(两行)
packages/cli/src/ui/components/InputPrompt.tsx:把slashCommands加入handleKeypress的依赖数组,位置紧邻同一分支读取的buffer与completion。packages/cli/src/ui/components/InputPrompt.test.tsx:给夹具中的memory命令补上真实命令所具有的action。
没有任何断言被修改、削弱或删除。共享该夹具的第三个测试 —— should autocomplete on Enter when user arrow-navigated a perfect-match suggestion list —— 此前之所以通过,只是因为对 /memory 而言完全匹配分支是死代码;夹具修正后,它真正走到了自己原本要验证的路径,并且依然通过。
变异探针(为每一行新增代码提供见证)
两处改动都在真实代码树上朝「失败方向」做过探针验证,而不是靠推断:
- 探针 A —— 依赖项。 当
slashCommands不在数组中时(即已合并的树),npm run lint:ci以退出码 1 失败,并报出1892:5 warning … missing dependency: 'slashCommands'。加上它之后,npm run lint:ci退出码为 0。两个方向都已实测。 - 探针 B —— 夹具的
action。 当夹具中memory条目的action: vi.fn()被移除时,上述两个测试以onSubmit的Number of calls: 0失败。加上它之后,该文件 215/215 全部通过。两个方向都在其余内容逐字节相同的树上实测(回退点上git diff为空)。
已报告但刻意未改动的部分
678ac2e1ec那次 runner 死亡本身。 属于基础设施问题:一个自托管 runner 在Run ESLint内部停止上报。仓库侧没有可修复的缺陷,也没有任何证据支持修改工作流,因此没有改动。这与 ci: size two job caps for a contended install, not a quiet one #10931(即被测试的那个 commit)正在为之调整超时时长的共享资源池争用是同一类现象;ci.yml的两条通道在 ECS 上已分别配有 120/90 分钟的超时上限,而这个 job 在 37 分钟就死了,远在上限之内。- 在本沙箱中运行整个
packages/cli套件时出现的 12 个文件、44 个失败属于环境问题,不是回归。 在回退本次修复后它们逐字节完全一致(同样的 12 个文件、同样的每文件计数:llm.test.tsx21 个、config.test.ts5 个、Footer.test.tsx4 个,另有 9 个文件各 1–3 个)。原因在输出中可直接看到:Error: EACCES: permission denied, mkdir '/home/github-runner/.qwen',以及 sandbox 镜像相关断言被本 agent 自身容器注入的SANDBOX/QWEN_CODE_CUSTOM_SANDBOX_IMAGE变量破坏。CI 不会设置这些变量。 - 本通道中的两处「假绿」,超出该 issue 范围,也超出本 bot 允许修改的区域。
node scripts/lint.js --sensitive-keywords(即Run sensitive keyword linter步骤)在scripts/lint.js的main()中没有对应分支,因此它什么都不检查就以退出码 0 结束;而--prettier运行的是prettier --write .,它会改写 runner 上的检出内容,对未格式化的输入不可能失败。两者都让这条通道看起来比实际更绿。选择上报而非修复:改动任一处都可能让main因为从未被度量过的违规而变红,而且两者都属于 CI 机制。
验证
以下命令确实在本检出中执行过;除特别说明外,都是在已提交的代码树上运行:
npm run build—— 通过(退出码 0)npm run typecheck—— 通过(退出码 0)npm run lint—— 通过(退出码 0)npm run lint:ci(失败 CI 步骤实际执行的命令)—— 通过(退出码 0)。修复前:退出码 1,并报出上文引用的 warning。- 在
packages/cli中执行npx vitest run src/ui/components/InputPrompt.test.tsx src/ui/components/InputPrompt.suggestionMouse.test.tsx—— 226 通过,0 失败(2 个文件)。修复前第一个文件为 213 通过 / 2 失败。 - 整个
packages/cli套件(npx vitest run)—— 28095 通过,44 失败;这 44 个在回退修复后完全相同地失败,属于上文所述的沙箱环境问题。为诚实起见列出,不计为通过。 node --test --test-concurrency=1 $HELPER_TESTS(全部 23 个辅助套件)—— 511 通过,0 失败(在诊断过程中运行;本次改动不涉及它们)。- 诊断过程中还运行了以下命令,全部通过:
bash .github/scripts/check-workflow-size.sh(使用 push 通道的 base SHA)、npm run audit:runtime:critical、npm run check:lockfile、npm run check:desktop-isolation、npm run check:tui-dep-direction、npm run check-i18n、npm run generate:settings-schema与npm run generate:notices --workspace=qwen-code-vscode-ide-companion(两者都没有改动代码树,说明两个新鲜度关卡均成立),以及对该 issue 的 commit 所触碰的两个工作流文件执行npx prettier --check。 npm run generate:settings-schema—— 并非必需(没有改动任何 settings 源),但仍如上执行过;packages/vscode-ide-companion/schemas/settings.schema.json保持干净,因此没有需要一并提交的再生成产物。npm run bundle之后的集成测试 —— 未运行。本次触碰的行为是单元级的 React 按键处理和一个测试夹具,上面的套件已直接覆盖;这里没有任何内容是只能通过打包后的 CLI 或集成测试框架才能触达的。actionlint/yamllint/shellcheck—— 未运行:这三个二进制在本容器中都没有安装,也没有可用于安装 yamllint 的pip3。本次改动只涉及一个.tsx依赖数组和一个测试夹具,与它们无关。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max-2026-09-02
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ — every required heading is filled in, plus the Chinese section. Problem: observed, and I confirmed it independently rather than taking the description's word for it.
So both halves of the stated problem are real and reproducible from main's own recorded runs. The Direction: aligned — Size: not applicable — no core paths ( Approach: scope feels exactly right, and I'd have written the same two lines. I tried to think of a smaller or safer edit and there isn't one: an Two observations for the record, neither blocking:
Risk: no elevated risk signals — neither changed file matches the high-risk path set, so standard review depth. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 所有必需小标题都填了,中文部分也在。 问题: 是已观测到的问题,而且我没有只采信 PR 描述,自己独立确认过。
所以 PR 所说的两半问题都真实存在,并且能从 main 自己记录的运行结果里复现。 方向: 对齐 —— main 现在是红的,这个 PR 就是修复,而且是从根因修,不是把 lint 规则消音。有一点需要如实说明,作为提问而不是阻拦: 规模: 不适用 —— 未触及核心路径( 方案: 范围拿捏得刚好,我自己也会写这两行。我尝试想过有没有更小或更安全的改法,结论是没有: 两点记录在案的观察,都不构成阻拦:
风险: 无升级风险信号 —— 两个改动文件都不匹配高风险路径集合,按常规深度审查即可。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewThe production line is the right fix, and it's load-bearing rather than lint appeasement. The staleness is also concretely reachable. The cost is nil, which is what I'd want to check before accepting a new dep on a hot keypress path: because the value is state and not a per-render literal, the callback is recreated only when the command list is (re)loaded, not on every render. It's also passed down as a plain The test line makes the fixture describe a state production can actually produce. With Blast radius of the fixture change — checked, not assumed:
No correctness, security, or convention problems, and nothing here that reads as over-abstraction or scope creep. Two lines doing one job. Test evidenceThis is an unattended CI run, so nothing was built or executed here — the review is static, and the evidence below is the PR's own CI read through the API plus main's recorded failures. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Nothing red on this head so far, and The baseline this PR has to clear is recorded, and it is the failure I quoted in the gate comment: on One pre-existing red to not misread later: Not verified, and why:
Sandboxed verification would settle both gaps, and the author has write access so neither lane needs sponsoring: 中文说明代码审查生产代码这一行是正确的修法,而且是真正起作用的,不只是让 lint 闭嘴。 这个过期捕获也是具体可达的。 代价为零,这也是我在接受热按键路径上新增依赖前想确认的:因为该值是 state 而不是每次渲染新建的字面量,回调只在命令列表(重新)加载时才重建,不是每次渲染都重建。它也是作为普通 测试这一行让 fixture 描述了一个生产确实可能出现的状态。 有了 fixture 改动的影响范围 —— 是查过的,不是假设的:
没有正确性、安全性或约定问题,也看不出过度抽象或范围蔓延。两行,做一件事。 测试证据这是无人值守的 CI 运行,所以本地没有构建或执行任何东西 —— 审查是静态的,下面的证据来自通过 API 读取的本 PR 自身 CI,以及 main 上已记录的失败。 (上表是审查时刻 目前这个 head 上没有红项,而 本 PR 需要跨过的基线是有记录的,也就是我在 gate 评论里引用的失败: 有一个既有红项,稍后别误读:同一 main tip 上还带着 未验证的部分及原因:
沙箱化验证可以把这两个缺口都补上,而且作者有 write 权限,两条通道都不需要赞助: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — clean two-line repair of a red I wrote my own proposal for this before opening the diff, from the title and the motivation alone: add the missing dependency to the keypress callback (not a suppression, not a ref mirror), and decide which side of the test mismatch is wrong — the fixture or the assertions — by checking whether production The one thing that would normally make me hesitate is the shape of the test fix: making a failing test pass by changing test data is how real regressions get buried. I pressed on it rather than accepting the framing. Production The production line is also more than lint hygiene, which is what lifts this above a chore: the command list starts as What is still open, and why I'm not approving in this run: Two notes for whoever merges, neither blocking:
On volume: this author has ten open PRs, so I checked that I was judging this one on its own evidence and not on a plausible-sounding description. The red Six months from now this reads fine: an honest dependency array and a fixture that describes a state the product can produce. 中文说明Confidence: 4/5 —— 一个干净的两行修复,修的是我自己独立确认过的红 在看 diff 之前,我只根据标题和动机写了自己的方案:给按键回调补上缺失的依赖(不是消音,也不是用 ref 镜像),然后判断测试不匹配里错的是哪一边 —— fixture 还是断言 —— 办法是确认生产里的 正常情况下会让我犹豫的一点,是这个测试修复的形态:靠改测试数据让失败的测试通过,正是真实回归被埋掉的常见方式。我没有接受它的说法,而是压上去查了。生产里的 生产代码那一行也不只是 lint 卫生问题,这正是它高于一次杂活的地方:命令列表初始是 仍未决的部分,也是我这轮不批准的原因:审查时本 head 上的 给合并者的两点备注,都不构成阻拦:
关于数量:这位作者有十个开放 PR,所以我特意确认了自己是在按它自身的证据判断,而不是被一段听起来合理的描述带走。 六个月后再看这段代码是舒服的:一个诚实的依赖数组,和一个描述了产品确实可能出现的状态的 fixture。 — 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 — CI landed green after the review. ✅
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; 213 passed — this review observed 28134 passed; 226 passed — this review observed 28134 passed.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):src/ui/components/InputPrompt.test.tsx — no such file or directory; 213 passed — this review observed 28134 passed; 226 passed — this review observed 28134 passed。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
E2E verification report (tmux, A/B against broken main) Verified head Leg 1 — Leg 2 — same eslint command on head → exit 0, clean. Leg 3 —
Leg 4 — same vitest file on head → exit 0, Independent review of the diff: the added Conclusion: the PR repairs both red lanes on |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round — no code changePR #10961 (issue #10944), head What the round received
So the only content to triage is the three clauses of that one review body. Disposition of
|
qqqys
left a comment
There was a problem hiding this comment.
Approving per the standing gate for this main-repair PR:
- ci-bot APPROVED this head (
6d023e9f04) at 18:00Z; its later 19:06Z pass was a partial re-review with an empty findings ledger (time-budget disclosure only, no findings). - CI at head: 22 success / 0 failure / 48 path-skips, nothing in flight.
- Our tmux A/B e2e report above (against broken main
661f41eef0) reproduced both red lanes exactly and showed both green at this head — conclusion: mergeable. - No critical issues found in our review.
批准理由(按既定门禁):
- ci-bot 已在当前 head(
6d023e9f04)于 18:00Z 批准;其 19:06Z 的后续评审为零发现的补审(仅披露时间预算缺口)。 - 当前 head CI 全绿:22 成功 / 0 失败 / 48 路径跳过。
- 上方 tmux A/B e2e 报告已在损坏的 main(
661f41eef0)上精确复现两条红色通道,并验证本 head 两条均转绿——结论:可合入。 - 我们的评审未发现 Critical 问题。
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No action this roundPR: #10961 · Issue: #10944 · Head: No code change was made and no commit was created. The feedback in this window contains nothing actionable. What the feedback said
There was no Independent verification of the report's claimsI did not take the verification report on trust. Both of its load-bearing code claims were checked against the exact reviewed commit, and both hold:
Commands run (all green)
CI corroborates: No mutation probes were run because this round added no guard, branch, or behavior — there is nothing new to witness. Two notes for the reviewer
ConclusionThe PR is a 2-line change (1 source, 1 test) that repairs both lanes red on 中文说明本轮无需改动PR: #10961 · Issue: #10944 · Head: 本轮未做任何代码改动,也未创建任何提交。本窗口内的反馈没有任何需要处理的内容。 反馈内容
反馈中没有 对报告结论的独立核实我没有直接采信这份验证报告。它两条关键性的代码论断都对照被审查的确切提交做了核实,两条都成立:
已执行的命令(全部通过)
CI 亦相互印证:本 head 的 本轮未运行变异探针(mutation probe),因为本轮没有新增任何守卫、分支或行为 —— 没有新东西需要见证。 给审查者的两点说明
结论本 PR 是一个 2 行改动(1 行源码、1 行测试),修复了自 #10929 合并以来 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
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.
What this PR does
Repairs the post-merge
Qwen Code CIlane onmain, which is red on its ESLint step, and the two unit tests that fail alongside it. Both trace to the change that made Enter resolve a/-prefixed input buffer against the live command list rather than the render-derived completion state.Two lines change. The keypress callback that reads the live command list now names it in its own dependency list, which is what
react-hooks/exhaustive-depsasks for and what the callback needs to avoid resolving commands from a list captured on an earlier render. And the test fixture'smemorycommand gains theactionthe real command has always had, so the two tests that assert "Enter on a perfect match submits" describe a state the real completion hook can actually produce.Why it's needed
mainis red now. The post-merge run for the current tip failsLint & Staticat itsRun ESLintstep with a recorded conclusion, and the lane runs ESLint with--max-warnings 0, so the singlemissing dependency: 'slashCommands'warning is a hard failure rather than noise.Beyond the gate, the missing dependency is the exact staleness the original change was written to remove: a submit handler holding an older command list resolves the user's live input against data from a previous render.
The two failing tests matter for a different reason. They stopped passing because the submit path began consulting the real command list instead of a mocked
isPerfectMatch, and the fixture declaredmemoryas a parent with subcommands but noaction— while the production command is runnable, and both the hook the old path used and the predicate the new path mirrors require anactionbefore calling a match perfect. The tests were therefore pinning a combination the product can never produce. Leaving them red keeps theTestlane failing on every merge and hides real regressions in the surrounding submit logic.Reviewer Test Plan
How to verify
mainwithout this PR, runnpm run lint:cifrom the repository root. Expect exit 1 withpackages/cli/src/ui/components/InputPrompt.tsx 1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands'. With this PR the same command exits 0.mainwithout this PR, runnpx vitest run src/ui/components/InputPrompt.test.tsxinsidepackages/cli. Expect 213 passed / 2 failed, both failures beingAssertionError: expected "spy" to be called with arguments: [ '/memory', …(1) ]withNumber of calls: 0, onshould submit directly on Enter after arrow-navigate + backspace + retype to perfect matchandshould submit directly on Enter for a perfect match without prior arrow navigation. With this PR the file is 215/215.memorycommand is runnable and opens the memory dialog, and the fixture'sexportentry in the same list already carried both anactionandsubCommands, so a runnable parent is the established shape here./memoryin the TUI and press Enter. It should submit and open the memory manager, not autocomplete to the first subcommand. Arrow-navigating the suggestion list first and then pressing Enter should still accept the highlighted suggestion rather than submit.Evidence (Before & After)
Non-UI change (a React hook dependency list and a test fixture), so no TUI capture. The observable before/after is the two gates:
npm run lint:ci→ exit 1,✖ 1 problem (0 errors, 1 warning)/ESLint found too many warnings (maximum: 0).InputPrompt.test.tsx→ 213 passed, 2 failed.npm run lint:ci→ exit 0.InputPrompt.test.tsx+InputPrompt.suggestionMouse.test.tsx→ 2 files, 226 passed, 0 failed.maintip reportsLint & Static→ step 22Run ESLint=failure.Tested on
Environment (optional)
Unit tests and lint only — no CLI run, no sandbox, no container. Verified in a Linux container on Node v22.23.2 against a clean
npm ciinstall.Risk & Scope
Lint & Staticjob died insideRun ESLintwith no step conclusion recorded — a self-hosted runner that stopped reporting mid-step, at 37 minutes against a 90-minute cap. That is infrastructure, has no repository-side repair, and is not addressed here; what this PR fixes is the reproducible failure of the same lane and step on the current tip. Also out of scope and only reported:node scripts/lint.js --sensitive-keywordshas no matching branch in the lint driver'smain(), so that step exits 0 without linting anything, and--prettierrunsprettier --write ., which cannot fail on unformatted input. Both are false greens in CI machinery; changing either could reddenmainon violations nobody has been measuring.Linked Issues
Fixes #10944
中文说明
这个 PR 做了什么
修复
main上合并后的Qwen Code CI通道 —— 它当前在 ESLint 步骤上是红的 —— 以及与之一起失败的两个单元测试。两者都源自那次「让 Enter 键针对以/开头的输入缓冲区,改为对照实时命令列表解析,而不再依赖由渲染派生的补全状态」的改动。改动共两行。读取实时命令列表的那个按键回调,现在在自己的依赖列表里声明了该列表:这既是
react-hooks/exhaustive-deps的要求,也是该回调避免用「上一次渲染时捕获的列表」去解析命令所必需的。另外,测试夹具中的memory命令补上了真实命令一直具有的action,于是那两个断言「完全匹配时按 Enter 应当提交」的测试,描述的是真实补全 hook 确实可能产生的状态。为什么需要它
main现在是红的。当前 tip 的合并后运行在Lint & Static的Run ESLint步骤上失败,并记录了明确结论;而该通道以--max-warnings 0运行 ESLint,所以那一条missing dependency: 'slashCommands'warning 是硬性失败,不是噪音。除了关卡本身,这个缺失的依赖恰恰是原改动想要消除的那种陈旧状态:一个持有旧命令列表的提交处理函数,会用上一次渲染的数据去解析用户的实时输入。
那两个失败的测试之所以重要,原因不同。它们不再通过,是因为提交路径开始查询真实命令列表而不是被 mock 的
isPerfectMatch;而夹具把memory声明为一个只有 subcommands、没有action的父命令 —— 但生产环境中该命令是可执行的,并且旧路径使用的 hook 与新路径所镜像的判定函数,都要求存在action才认定「完全匹配」。因此这两个测试固定住的是产品永远不可能产生的组合。让它们继续红着,会使每次合并都让Test通道失败,并掩盖周边提交逻辑中的真实回归。审阅者测试计划
如何验证
main上,从仓库根目录运行npm run lint:ci。预期退出码 1,并报出packages/cli/src/ui/components/InputPrompt.tsx 1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands'。带上本 PR 后,同一命令退出码为 0。main上,进入packages/cli运行npx vitest run src/ui/components/InputPrompt.test.tsx。预期 213 通过 / 2 失败,两个失败都是AssertionError: expected "spy" to be called with arguments: [ '/memory', …(1) ]且Number of calls: 0,分别位于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。带上本 PR 后该文件为 215/215。memory命令是可执行的、会打开 memory 管理器;而同一列表中夹具的export条目本来就同时带有action和subCommands,所以「可执行的父命令」正是这里既有的形态。/memory并按 Enter。它应当提交并打开 memory 管理器,而不是自动补全成第一个子命令。而先用方向键在建议列表中导航、再按 Enter,仍应接受高亮的建议,而不是提交。证据(改动前与改动后)
非 UI 改动(一个 React hook 依赖列表和一个测试夹具),因此没有 TUI 录制。可观察的前后差异就是这两个关卡:
npm run lint:ci→ 退出码 1,✖ 1 problem (0 errors, 1 warning)/ESLint found too many warnings (maximum: 0)。InputPrompt.test.tsx→ 213 通过,2 失败。npm run lint:ci→ 退出码 0。InputPrompt.test.tsx+InputPrompt.suggestionMouse.test.tsx→ 2 个文件,226 通过,0 失败。maintip 的合并后运行报告Lint & Static→ 第 22 步Run ESLint=failure。测试环境
运行环境(可选)
仅单元测试与 lint —— 没有运行 CLI,没有 sandbox,没有容器。在 Linux 容器内、Node v22.23.2、基于干净的
npm ci安装验证。风险与范围
Lint & Staticjob 死在Run ESLint内部,且没有记录步骤结论 —— 这是一个自托管 runner 在步骤中途停止上报,发生在 37 分钟处,而上限是 90 分钟。那属于基础设施问题,仓库侧无从修复,本 PR 也不处理它;本 PR 修复的是当前 tip 上同一通道、同一步骤的可复现失败。同样超出范围、仅作上报:node scripts/lint.js --sensitive-keywords在 lint 驱动程序的main()中没有对应分支,所以该步骤什么都不检查就以退出码 0 结束;而--prettier运行的是prettier --write .,对未格式化的输入不可能失败。两者都是 CI 机制中的「假绿」;改动任一处都可能让main因为从未被度量过的违规而变红。关联 Issue
Fixes #10944