fix(cli): restore green main CI after the live slash-command submit change (#10967) - #10968
qwen-code-dev-bot wants to merge 2 commits into
Conversation
…hange (#10967) Commit 661f41e (#10929) reddened both post-merge CI jobs on main, and every run since has repeated the same two failures. Lint & Static / "Run ESLint": the submit path now reads `slashCommands` inside the keypress `useCallback`, but the dependency array was not updated. CI runs `lint:ci` (`--max-warnings 0`), so the single react-hooks/exhaustive-deps warning fails the job even though `npm run lint` only warns. Add the dependency. Test / "Run tests and generate reports": Enter on a slash buffer now decides "perfect match" from the live buffer and the real command list instead of the render-derived completion state. Two pre-existing regression tests drive `/memory` through a mocked completion that claims a perfect match while their mocked `memory` command is a container with no `action` — a state the real hook cannot produce, because `usePerfectMatch` requires an action and every real command that can be typed exactly (including the real `memoryCommand` and MCP prompt parents) defines one. Give the mock an `action` so both tests exercise a genuine perfect match again; no assertion is weakened, and the neighbouring "navigated perfect match autocompletes" test now covers the branch it previously reached only by accident. Fixes #10967.
Autofix report — issue #10967 (main CI failure on
|
|
|
|
Thanks for the PR! Template looks good ✓ — every required heading is present, including the reviewer test plan and the Chinese translation. Problem: observed, not theoretical — and I confirmed it from the CI logs rather than taking the description's word for it. Run 33797332027 on Direction: restoring the two required checks on Size: not applicable. Approach: the scope feels right, and I could not find a smaller one. I considered the three obvious alternatives and each is worse: an inline One thing I want to be explicit about, because it is the only way this PR could have been wrong: editing a test fixture to make a test pass is usually a red flag. Here it is the correct fix, and the reason is that the mocked state was unreachable in production — the details are in the code review below. Risk: no elevated risk signals — no Stage 1e high-risk path matched. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 所有必需小标题都在,包括审阅者测试计划和中文翻译。 问题: 是已观测到的失败,不是理论性加固 —— 而且我是从 CI 日志里自己确认的,没有只采信 PR 描述。 方向: 恢复 规模: 不适用。按 Stage 0 的定义, 方案: 范围合理,我也找不到更小的方案。我考虑过三种显而易见的替代做法,都更差:行内 有一点我想明确说明,因为这是本 PR 唯一可能出错的地方:为了让测试通过而去改测试 fixture,通常是个危险信号。这里却是正确的修法,原因在于被 mock 出来的那个状态在生产环境中根本不可达 —— 细节在下面的代码审查里。 风险: 无升级风险信号 —— Stage 1e 的高风险路径没有命中。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewBefore reading the diff I wrote down what I would have done from the title and the "why" alone: add the missing dependency to the callback's dependency array (an inline The PR's approach matches mine, and the diagnosis holds up under checking:
Collateral check on the shared fixture, since No critical blockers and no AGENTS.md violations. Two non-blocking observations for a follow-up, neither of which belongs in this PR:
TestingThis is an unattended CI run, so per the gate's rules I did not build or execute anything from this PR. The evidence below is the PR's own CI, read through the API, plus the base-commit logs I quoted in Stage 1 — not the author's local results. I have not re-run the suite myself and am not presenting the description's local numbers as verification. The two gates this PR targets, Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Real-scenario testing: N/A — nothing user-visible changes, and on an unattended CI run the live-behaviour lane is not driven from here. No sandboxed verification lane applies either: the claim on trial is "the two required gates go green", which is exactly what the CI signal above settles, and there is no behavioural delta for 中文说明在看 diff 之前,我先只根据标题和「为什么需要」写下了自己的做法:把缺失的依赖加进回调的依赖数组(行内 本 PR 的做法与我的一致,诊断经得起核对:
共享 fixture 的连带影响检查( 没有阻断性问题,也没有违反 AGENTS.md。两点不阻断的后续观察,都不属于本 PR 的范围:
测试证据: 这是一次无人值守的 CI 运行,按门禁规则我没有构建或执行本 PR 的任何代码。下面的证据是 PR 自己的 CI(通过 API 读取),加上我在 Stage 1 引用的基线 commit 日志 —— 不是作者的本地结果。我没有自己重跑测试套件,也不会把描述里的本地数字当作验证呈现。 本 PR 针对的两条门禁 真实场景测试:N/A —— 没有用户可见的变化,而且无人值守 CI 运行不会从这里驱动实时行为验证。也不适用任何沙箱验证通道:本次待判定的主张就是「两条必需门禁变绿」,而这正是上面 CI 信号能够判定的内容;不存在可供 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — clean across every stage and I could not break it; the two reservations I have are follow-ups that predate this PR, and the one thing still outstanding is CI itself. Stepping back: this is the rare two-line PR where the honest question is not "is the code good" but "is it allowed to be this small". A red The place a PR like this does damage, if it does damage, is the test-fixture edit — "make the mock satisfy the test" is how a real regression gets buried. So that is where I spent the review, and it holds: the state the mock described (a command with subcommands, no action, reported as a perfect match) cannot occur in production, because both perfect-match gates require an executable action and the real The runtime line is the other place to be suspicious, and it is inert: the callback already carried a fresh-every-render object in its dependency array, so it was never actually memoized. Adding the entry satisfies the linter without changing a single observable behavior. Six months from now this reads as the fix the lint rule was asking for, not as a workaround. What keeps this at 4 rather than 5: the two follow-ups named in the review — the render-derived perfect-match flag is now dead for slash buffers, so the mocked Verdict: approve — deferred until CI lands green. The two gates this PR exists to fix, 中文说明信心:4/5 —— 每个阶段都干净,我没能把它找出破绽;我保留的两点都是早于本 PR 的后续事项,而唯一还悬着的就是 CI 本身。 退一步看:这是那种少见的两行 PR,真正该问的不是「代码好不好」,而是「它是否可以小到这个程度」。 这类 PR 如果真会造成损害,损害点就在测试 fixture 的那处改动上 —— 「让 mock 去满足测试」正是真实回归被埋掉的方式。所以我把审查的力气花在了那里,而它站得住:mock 描述的那个状态(一个有子命令、没有 action、却被报告为 perfect match 的命令)在生产中不可能出现,因为两条 perfect-match 判定都要求存在可执行 action,而真实的 运行时那一行是另一个该保持怀疑的地方,而它是惰性的:该回调的依赖数组里本来就有一个每次渲染都重建的对象,所以它从未真正被 memo 住。加上这一项只是满足了 linter,不改变任何可观测行为。半年后再看,这就是 lint 规则本来要求的那个修复,而不是一个绕法。 之所以是 4 而不是 5:审查里点名的两个后续事项 —— 对斜杠缓冲区而言,渲染派生的 perfect-match 标志已经失效,所以这三个测试里 mock 的 结论:批准 —— 但推迟到 CI 转绿。 本 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.
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)
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
Local verification report — PR #10968Verdict: the change is correct and every claim in the PR body reproduces, but this PR is now an empty diff against 1. The PR no longer changes anything
Everything below verifies the change itself, so the record is complete whichever way it is closed. 2. EnvironmentFresh git worktree at the PR head 3. Gate replay (Reviewer Test Plan steps 1–4)
Diff confirmed as exactly two added lines ( 4. Mutation probes (step 5) — both lines are load-bearingRun at the PR head, file restored with
5. Real TUI behaviour (steps 6–7), with an A/B against the pre-#10929 buildFour probes, each in a fresh CLI session, run on the PR-head bundle and again on the
Seven pane captures per arm were diffed after stripping blanks: all identical except the random tip line and the session UUID inside the 6. One correction to the PR narrative (does not change the conclusion)The body says every real command that can be typed exactly defines an 7. CI on this PR, as of posting
8. Not covered
中文版PR #10968 本地验证报告结论:改动本身正确,PR 描述中的每一条声明都能复现;但这个 PR 现在相对 1. 这个 PR 已经不再改变任何东西
下面的内容用于完整地验证改动本身,无论最终怎么关闭都留有记录。 2. 环境在 PR head 3. 门禁复现(Reviewer Test Plan 第 1–4 步)
已确认 diff 恰好是两行新增( 4. 变异验证(第 5 步)—— 两行都是起作用的在 PR head 上执行,每次探针后用
5. 真实 TUI 行为(第 6–7 步),并与 #10929 之前的构建做 A/B四个探针,每个都在全新的 CLI 会话中执行,分别跑在 PR head 的 bundle 和
每个分支各 7 张终端截屏,去掉空行后逐一 diff:除随机 tip 行和 截图见上方英文部分(图 1–5)。 6. 对 PR 叙述的一处更正(不影响结论)PR 正文称“所有可以被完整输入的真实命令都定义了 7. 发布本评论时该 PR 的 CI 状态
8. 未覆盖的部分
|





What this PR does
Restores
mainto green by repairing the two CI jobs that have failed on every post-merge run since the live slash-command submit change landed. The submit path that reads the available slash commands now declares that input in its memoization dependencies, satisfying the lint gate CI enforces. The two regression tests that stopped passing are made to describe a real perfect match again: their mocked command was a container with no executable action, a state the completion hook can never report as a perfect match, so the mock now carries an action the way the real command does. Two lines change in total; no assertion is weakened and no runtime behavior is altered.Why it's needed
Qwen Code CIonmainhas been red since the commit that made Enter decide a slash-command perfect match from the live input buffer instead of the render-derived completion state. That commit read the slash-command list inside the keypress callback but did not add it to the dependency array, which thereact-hooks/exhaustive-depsrule flags; CI's ESLint step runs with--max-warnings 0, so a single warning fails the wholeLint & Staticjob and skips every static check after it. The same commit broke two pre-existing tests that drive/memorythrough a mocked completion claiming a perfect match while their mockedmemorycommand has subcommands and no action. Production never produces that combination — the real perfect-match logic requires an executable action, and every real command that can be typed exactly defines one, including the real memory command and MCP prompt parents — so the tests were asserting against a mock artifact rather than catching a user-facing regression. Because both jobs are required contexts,mainreports failure on every merge until this is fixed.Reviewer Test Plan
How to verify
mainatcca376f6aaand runnpm run lint:ci. Expect exit code 1 with exactly one warning:packages/cli/src/ui/components/InputPrompt.tsx 1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands' react-hooks/exhaustive-deps. Note thatnpm run lint(without--max-warnings 0) only warns, which is why this looked clean locally.cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx. Expect2 failed | 213 passed (215), failing 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, both withexpected "spy" to be called with arguments: [ '/memory', …(1) ] / Number of calls: 0.lint:ciexit code 0 with no output, and215 passed (215).slashCommands,in the keypress callback's dependency array, andaction: vi.fn(),on the mockedmemorycommand.actionmust fail those same two tests again (npx vitest run src/ui/components/InputPrompt.test.tsx -t "perfect match"→2 failed | 1 passed). Removing the dependency must failnpx eslint packages/cli/src/ui/components/InputPrompt.tsx --max-warnings 0with exit code 1.should autocomplete on Enter when user arrow-navigated a perfect-match suggestion liststill passes: with an actionable/memory, arrow-navigating and pressing Enter must still autocomplete the highlighted subcommand rather than submit, which is the behavior that test pins.Evidence (Before & After)
N/A — no user-visible/TUI change. The evidence is the two CI gates themselves:
cca376f6aa)npm run lint:ciESLint found too many warnings (maximum: 0), 1react-hooks/exhaustive-depswarningvitest run src/ui/components/InputPrompt.test.tsx2 failed | 213 passed (215)215 passed (215)Lint & Static→Run ESLintTest→Run tests and generate reportsTested on
Environment (optional)
Linux self-hosted runner checkout, Node v22.23.2, npm 10.9.8, unit tests only (no CLI run, no sandbox, no live model calls). Test runs were additionally repeated under a CI-like environment (
SANDBOXunset, temporaryHOME, all API keys emptied) to rule out environment dependence:InputPrompt.test.tsxis 215/215 in both.Risk & Scope
packages/cliworkspace run in this sandbox reports 33 failures in 9 unrelated files (settings/config, Windows-style home paths, browser-open commands, serve live-host discovery, Footer snapshots). These are sandbox artifacts, notmainfailures: the Footer snapshot diff is an extradocker |pill because the shell runs inside Docker withSANDBOXset, and re-running that file withSANDBOXunset and a cleanHOMEgives 34/34 instead of 4 failures. CI history corroborates — theTestjob was green at678ac2e1ec, the commit immediately before the one that touched only these two files. Also out of scope: the earlierLint & Staticjob-level failure on run 33773880468 (678ac2e1ec), which reported no failed step and is a separate transient infrastructure event, not the failure tracked by Main CI failed: Qwen Code CI on cca376f6aa4b #10967.Linked Issues
Fixes #10967
中文说明
这个 PR 做了什么
让
main恢复绿色:修复自“实时斜杠命令提交”改动落地以来、每次合并后运行都失败的两个 CI job。读取可用斜杠命令的提交路径现在把这一输入声明进了它的 memo 依赖数组,从而满足 CI 强制执行的 lint 门禁。两个不再通过的回归测试被重新改为描述一个真实的“完全匹配”:它们 mock 的命令是一个没有可执行 action 的容器命令,而 completion hook 永远不可能把这种状态报告为完全匹配,因此该 mock 现在像真实命令一样带上了 action。总共只改动两行;没有削弱任何断言,也没有改变任何运行时行为。为什么需要
自那个让 Enter 依据实时输入缓冲区(而不是渲染派生的 completion 状态)来判定斜杠命令完全匹配的 commit 起,
main上的Qwen Code CI一直是红的。该 commit 在按键回调内部读取了斜杠命令列表,却没有把它加入依赖数组,react-hooks/exhaustive-deps规则会标记这一点;而 CI 的 ESLint 步骤以--max-warnings 0运行,所以一条警告就会让整个Lint & Staticjob 失败,并跳过其后的所有静态检查。同一个 commit 还破坏了两既有测试:它们用/memory驱动,其 mock 的 completion 声称完全匹配,而它们 mock 的memory命令有子命令却没有 action。生产环境永远不会产生这种组合 —— 真实的完全匹配逻辑要求存在可执行的 action,而所有可以被完整输入的真实命令都定义了 action(包括真实的 memory 命令和 MCP prompt 父命令)—— 所以这些测试断言的是 mock 造成的假象,而不是捕获了面向用户的回归。由于这两个 job 都是必需检查项,在修复之前每次合并main都会报告失败。审阅者测试计划(Reviewer Test Plan)
如何验证
main的cca376f6aa,运行npm run lint:ci。预期退出码为 1,并且恰好只有一条警告:packages/cli/src/ui/components/InputPrompt.tsx 1892:5 warning React Hook useCallback has a missing dependency: 'slashCommands' react-hooks/exhaustive-deps。注意npm run lint(没有--max-warnings 0)只会警告,这正是它在本地看起来“干净”的原因。cd packages/cli && npx vitest run src/ui/components/InputPrompt.test.tsx。预期2 failed | 213 passed (215),失败的是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。lint:ci退出码为 0 且无输出,测试为215 passed (215)。slashCommands,,以及 mock 的memory命令上的action: vi.fn(),。action必须让同样的两个测试再次失败(npx vitest run src/ui/components/InputPrompt.test.tsx -t "perfect match"→2 failed | 1 passed)。移除依赖项必须让npx eslint packages/cli/src/ui/components/InputPrompt.tsx --max-warnings 0以退出码 1 失败。should autocomplete on Enter when user arrow-navigated a perfect-match suggestion list依然通过:当/memory可执行时,用方向键导航后按 Enter 仍必须自动补全高亮的子命令,而不是提交 —— 这正是该测试钉住的行为。证据(改动前与改动后)
N/A —— 没有用户可见/TUI 变化。证据就是这两个 CI 门禁本身:
cca376f6aa)npm run lint:ciESLint found too many warnings (maximum: 0),1 条react-hooks/exhaustive-deps警告vitest run src/ui/components/InputPrompt.test.tsx2 failed | 213 passed (215)215 passed (215)Lint & Static→Run ESLintTest→Run tests and generate reports测试环境(Tested on)
环境(可选)
Linux 自托管 runner 检出,Node v22.23.2,npm 10.9.8,仅单元测试(未运行 CLI、未使用沙箱、未发起真实模型调用)。为排除环境依赖,测试还在类 CI 环境(取消
SANDBOX、使用临时HOME、清空所有 API key)下重复运行:InputPrompt.test.tsx在两种环境下均为 215/215。风险与范围
packages/cliworkspace 跑全量测试会报出 9 个无关文件中的 33 个失败(settings/config、Windows 风格的 home 路径、拉起浏览器的命令、serve live-host 发现、Footer 快照)。它们是沙箱产物,而非main的失败:Footer 快照差异是多出一个docker |标记,因为这个 shell 运行在 Docker 内部且设置了SANDBOX;取消SANDBOX并使用干净HOME重跑该文件,结果是 34/34 而不是 4 个失败。CI 历史也佐证了这一点 ——Testjob 在678ac2e1ec(紧接在只改动这两个文件的那个 commit 之前)是绿色的。同样超出范围:运行 33773880468(678ac2e1ec)中更早的那次Lint & Staticjob 级失败,它没有报告任何失败的步骤,属于另一次瞬时的基础设施事件,而不是 Main CI failed: Qwen Code CI on cca376f6aa4b #10967 跟踪的失败。关联 Issue
Fixes #10967