Skip to content

test(cli): pin pid liveness in the review cleanup unlink case - #15751

Merged
yiliang114 merged 3 commits into
mainfrom
fix/review-cleanup-pid-liveness
Oct 28, 2026
Merged

yiliang114 merged 3 commits into
mainfrom
fix/review-cleanup-pid-liveness

Conversation

@yiliang114

@yiliang114 yiliang114 commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Pins process liveness in the one orphan-reap case of the review cleanup suite that trusted a hardcoded pid to be absent from the host's process table. The case now stubs the liveness probe for the duration of the sweep, using the same helper as two sibling orphan cases in the same block, so the fixture's "this launcher is dead" premise is a fact of the test instead of an assumption about the machine it runs on.

Why it's needed

Main CI failed: Qwen Code CI — src/commands/review/cleanup.test.ts > â€Ķ > keeps the run alive when the post-kill UNLINK fails (#15737, run 40547925273) is not a defect in the unlink guard it is meant to pin. The case builds an orphan socket named for pid 626262 and never stubs process.kill, so when a busy runner happens to have a process at that pid the sweep correctly treats the socket as alive, skips it, and reports Nothing to clean for target "local" — an output that reads exactly like the guard under test having been removed. Giving the same fixture a pid that is certainly resident reproduces that CI output on an otherwise green tree, and the pinned fixture passes under the same condition.

Reviewer Test Plan

How to verify

Run the suite (cd packages/cli && npx vitest run src/commands/review/cleanup.test.ts) and confirm 109 tests pass. To see the failure this prevents, rename the fixture in that one case to a pid you know is alive (for example process.pid) and drop the new wrapper: the case then fails with "Nothing to clean for target \"local\"." instead of the assertion about the reaped line. Restoring the wrapper makes it pass with the same resident pid.

Evidence (Before & After)

Before — old assertion, fixture pid made resident:

× runCleanup > orphaned capture-tui servers > keeps the run alive when the post-kill UNLINK fails 9ms
+   "Nothing to clean for target \"local\".",
      Tests  1 failed | 108 skipped (109)

After — pinned probe, same resident fixture pid:

✓ src/commands/review/cleanup.test.ts (109 tests | 108 skipped) 4ms
      Tests  1 passed | 108 skipped (109)

Full file after the change: 109 passed.

Tested on

OS Status
🍏 macOS ✅
🊟 Windows ⚠ïļ
🐧 Linux ⚠ïļ

Environment (optional)

A local worktree of main at 9acb3a5a1d68a, installed with corepack pnpm install --frozen-lockfile and built with npm run build; unit tests only.

Risk & Scope

  • Main risk or tradeoff: none for product code; the change is confined to one test case and only replaces an assumption about the host's pid space with an explicit stub.
  • Not validated / out of scope: the Windows and Linux lanes were not run locally. The enclosing block is describe.skipIf(process.platform === 'win32'), so only the POSIX lanes execute this case.
  • Breaking changes / migration notes: none.

Linked Issues

Closes #15737

äļ­æ–‡čŊŽ

čŋ™äļŠ PR 做䚆äŧ€äđˆ

įŧ™ review cleanup æĩ‹čŊ•里å”Ŋäļ€äļ€äļŠäūčĩ–"写æ­ŧįš„ pid åœĻåŪŋäļŧ朚äļŠäļå­˜åœĻ"įš„å­Īå„ŋčŋ›įĻ‹į”Ļäū‹čĄĨäļŠå­˜æīŧæŽĒé’ˆįš„æĄĐ。čŊĨį”Ļäū‹įްåœĻåœĻäļ€æŽĄæ‰Ŧ描期é—ī囚åۚ process.kill įš„čŋ”回åĪį”Ļ同äļ€äŧĢį å—é‡ŒåĶåĪ–äļĪäļŠå­Īå„ŋį”Ļäū‹ä―ŋį”Ļįš„ helper䚎æ˜Ŋ"čŋ™äļŠåŊåŠĻå™Ļå·ēįŧæ­ŧ乆"äŧŽåŊđčŋčĄŒæœšå™Ļįš„å‡čŪū变成æĩ‹čŊ•č‡Šå·ąįŧ™å‡šįš„äš‹åŪžã€‚

äļšäŧ€äđˆéœ€č́

Main CI failed: Qwen Code CI — src/commands/review/cleanup.test.ts > â€Ķ > keeps the run alive when the post-kill UNLINK fails#15737run 40547925273åđķäļæ˜Ŋåۃæƒģč́åŪˆä―įš„é‚ĢæŪĩ unlink 兜嚕é€ŧčū‘įš„įžšé™·ã€‚čŊĨį”Ļäū‹æž„造䚆äļ€äļŠäŧĨ 626262 å‘―åįš„å­Īå„ŋ socketåīäŧŽæœŠæĄĐ掉 process.killïž›å―“įđåŋ™įš„ runner ä°åĨ―存åœĻčŋ™äļŠ pid æ—ķæ‰Ŧ描䞚æ­ĢįĄŪ地把åۃåˆĪäļšå­˜æīŧåđķč·ģčŋ‡ïžŒæœ€åŽæ‰“印 Nothing to clean for target "local"——čŋ™äļŠčū“å‡šįœ‹čĩ·æĨ和"兜嚕čĒŦ删掉"äļ€æĻĄä ·ã€‚把čŋ™äļŠį”Ļäū‹įš„囚åۚ pid æĒ成äļ€äļŠåŋ…į„ķ存æīŧįš„ pidïžŒå°ąčƒ―åœĻå…ķåۃæĩ‹čŊ•å…Ļįŧŋįš„å·Ĩä―œåŒšé‡ŒåĪįŽ°å‡šåŒäļ€čĄŒ CI čū“å‡šïž›č€ŒåœĻåŒæ ·įš„æĄäŧķäļ‹ïžŒå›šåۚ存æīŧæŽĒ针äđ‹åŽį”Ļäū‹å°ąčƒ―通čŋ‡ã€‚

čŊ„åŪĄæ–đ垏

čŋčĄŒčŊĨæĩ‹čŊ•æ–‡äŧķcd packages/cli && npx vitest run src/commands/review/cleanup.test.tsįĄŪčŪĪ 109 äļŠį”Ļäū‹å…ĻéƒĻ通čŋ‡ã€‚åĶ‚æžœæƒģįœ‹åˆ°åۃé˜ēæ­Ēįš„åĪąčīĨ把čŊĨį”Ļäū‹įš„囚åۚ pid æĒ成äļ€äļŠä― įĄŪåۚ存æīŧįš„ pidäū‹åĶ‚ process.pidåđķåŽŧæŽ‰æ–°åŠ įš„åŒ…čĢ…ïžšį”Ļäū‹äžšäŧĨ "Nothing to clean for target \"local\"." åĪąčīĨïžŒč€Œäļæ˜Ŋå…ģ䚎 reap čū“å‡šįš„æ–­čĻ€åĪąčīĨ把包čĢ…åŠ å›žæĨ后åœĻ同äļ€äļŠå­˜æīŧ pid äļ‹å°ąäžšé€ščŋ‡ã€‚

čŊæŪäŋŪæ”đ前后

äŋŪæ”đ前——旧断čĻ€ïžŒå›šåۚ pid 恰åĨ―存æīŧïžš

× runCleanup > orphaned capture-tui servers > keeps the run alive when the post-kill UNLINK fails 9ms
+   "Nothing to clean for target \"local\".",
      Tests  1 failed | 108 skipped (109)

äŋŪæ”đ后——囚åۚ存æīŧæŽĒ针同äļ€äļŠå­˜æīŧįš„å›šåۚ pidïžš

✓ src/commands/review/cleanup.test.ts (109 tests | 108 skipped) 4ms
      Tests  1 passed | 108 skipped (109)

äŋŪæ”đ后æ•īäŧ―文äŧķïžš109 äļŠį”Ļäū‹é€ščŋ‡ã€‚

朎地銌čŊčŒƒå›ī

朎地åœĻ main9acb3a5a1d68aïž‰įš„į‹ŽįŦ‹å·Ĩä―œåŒšé‡Œį”Ļ corepack pnpm install --frozen-lockfile åŪ‰čĢ…ã€npm run build 构åŧšïžŒåŠč·‘䚆单元æĩ‹čŊ•。Windows äļŽ Linux äļĪæĄ lane 朊åœĻ朎地čŋčĄŒïž›čŊĨäŧĢį å—æ˜Ŋ describe.skipIf(process.platform === 'win32')及有 POSIX lane äžšæ‰§čĄŒčŋ™äļŠį”Ļäū‹ã€‚

éĢŽé™ĐäļŽčŒƒå›ī

及æ”đäļ€äļŠæĩ‹čŊ•į”Ļäū‹ïžŒäļæķ‰åŠäš§å“äŧĢį ïž›æŠŠåŊđåŪŋäļŧ朚 pid įĐšé—īįš„å‡čŪūæĒ成æ˜ū垏æĄĐæēĄæœ‰å…ķåŪƒå―ąå“ã€‚

å…ģ联 issue

Closes #15737

The orphan-reap fixture named for pid 626262 never stubbed the liveness
probe, so a runner with a process at that pid made the sweep skip the
orphan as alive and report 'Nothing to clean', which reads as the unlink
guard under test being broken. Stub the probe like the five sibling
cases already do.
@qwen-code-review-bot

qwen-code-review-bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

âģ Approval still deferred — 1 PR CI workflow run(s) still in progress for 9aadf13; the finalize job approves automatically once every run lands green. finalize run

âģ åŪĄæ‰đäŧåœĻåŧķčŋŸäļ­ â€”â€” 9aadf13 čŋ˜æœ‰ 1 äļŠ PR CI workflow åœĻčŋčĄŒïžŒå…ĻéƒĻ通čŋ‡åŽ finalize äŧŧåŠĄäžšč‡ŠåŠĻ提äšĪåŪĄæ‰đ。æŸĨįœ‹ finalize čŋčĄŒ

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all nine required headings present, with a real before/after and the OS matrix filled in.

Problem: observed, not theoretical. Test (ubuntu-latest, Node 22.x) on run 40547925273 failed this exact case on all three attempts (retry x2), and the job log carries precisely the assertion diff the PR quotes:

-   "Reaped orphaned capture server: qwen-review-capture-626262-aaa",
+   "Nothing to clean for target \"local\".",
 âŊ src/commands/review/cleanup.test.ts:1562:41

I checked the diagnosis against the production code rather than taking it on trust. cleanup.ts probes with process.kill(pid, 0) and sets alive = code !== 'ESRCH', then if (alive) continue; — so a resident pid 626262 makes the sweep skip the socket before it ever reaches the unlink guard. That also explains why the failure landed on the second assertion: expect(...).not.toThrow() still passed, so nothing here contradicts the guard the case exists to pin. The runner was self-hosted and loaded (load[6.76 15.45 18.25], hosttests[17]), which is the condition the hypothesis needs.

Direction: aligned — CI flake removal on a suite this repo already treats as load-bearing. No CHANGELOG reference (test-only), and none expected.

One note on the linked issue, because the duplicate gate looks at it: #15737 is closed as completed but not by a merged PR — the closer resolves to null. You closed it yourself eight minutes before opening this PR, on the rationale that the signature "has not recurred since". I verified the change is genuinely absent from main at 9acb3a5a1d68a (that case still calls runCleanup('local') bare, and withDeadPids has only its two pre-existing call sites), so this is not a duplicate of a landed fix and I have not closed it. Flagging the manual close only because the gate cannot distinguish "flake stopped by luck" from "flake fixed" — and this PR is arguing the former is worth hardening anyway, which I agree with: a non-recurrence is not a fix, and the next busy runner can land on 626262 again.

Size: not applicable — no core paths (single file under packages/cli/src/commands/review/), and 0 production logic lines since the only changed file is *.test.ts. No Tier 1/Tier 2 concern, no large-PR advisory.

Approach: minimal, and it matches the neighborhood. Of the literal-pid cases in this block, 717171/727272 and 838383 already wrap in withDeadPids, while 515151 and 424242 stub process.kill inline because they need the opposite probe answer — so 626262 really was the only literal-pid case with no liveness control. I had considered the other available route, reusing the block's deadPid (a spawned-then-reaped child pid that eight earlier cases already rely on); it is a smaller diff but a weaker one — still a host assumption about pid reuse rather than a fact of the test — and it would churn the fixture name in the assertion. Your version is the better call.

Small description nit, not a code issue: "exactly as the five sibling orphan cases in the same block already do" overstates the precedent — two siblings use withDeadPids, and two more stub the probe to the opposite effect. Worth a tweak so the next reader is not sent looking for five.

Risk: no elevated risk signals — no high-risk path matched (and the file is excluded as a test regardless).

Moving on to code review. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪ

æĻĄæŋåیæ•ī ✓ —— äđäļŠåŋ…需标éĒ˜é―å…ĻïžŒé™„äš†įœŸåŪžįš„ before/afterOS įŸĐé˜ĩäđŸåĄŦ䚆。

é—ŪéĒ˜ïžš æ˜Ŋå·ē观æĩ‹åˆ°įš„äļæ˜ŊᐆčŪšæ€§åŠ å›šã€‚run 40547925273 įš„ Test (ubuntu-latest, Node 22.x) åœĻäļ‰æŽĄå°čŊ•retry x2ïž‰é‡Œéƒ―åĪąčīĨåœĻčŋ™äļŠį”Ļäū‹äļŠïžŒjob æ—Ĩåŋ—é‡Œįš„æ–­čĻ€ diff äļŽ PR åž•į”Ļįš„åیå…Ļäļ€č‡ī期望 Reaped orphaned capture server: qwen-review-capture-626262-aaaåŪžé™…åū—到 Nothing to clean for target "local".ïžŒä―į―Ū cleanup.test.ts:1562。

我æēĄæœ‰į›īæŽĨ采äŋĄčŋ™äļŠå―’å› ïžŒč€Œæ˜ŊåŊđį€äš§å“äŧĢ᠁æ ļ乆äļ€éã€‚cleanup.ts į”Ļ process.kill(pid, 0) æŽĒæīŧalive = code !== 'ESRCH'随后 if (alive) continue; —— 所äŧĨ及č́åŪŋäļŧ朚äļŠ pid 626262 恰åĨ―存æīŧæ‰Ŧæå°ąäžšåœĻč§Ķ及 unlink 兜嚕äđ‹å‰č·ģčŋ‡čŋ™äļŠ socket。čŋ™äđŸč§Ģ释乆äļšäŧ€äđˆåĪąčīĨč―åœĻįŽŽäšŒäļŠæ–­čĻ€äļŠïžšexpect(...).not.toThrow() äūį„ķ通čŋ‡ïžŒå› æ­Īčŋ™é‡ŒæēĄæœ‰äŧŧä―•čŊæŪäļŽåۃč́åŪˆįš„é‚ĢæŪĩ兜嚕é€ŧčū‘į›ļįŸ›į›ūã€‚å―“æ—ķįš„ runner æ˜Ŋč‡Šæ‰˜įŪĄäļ”čīŸč――åūˆéŦ˜įš„load[6.76 15.45 18.25]、hosttests[17]æ­Ģæ˜Ŋčŋ™äļŠå‡čŪū需čĶįš„æĄäŧķ。

æ–đ向 åŊđé― —— čŋ™æ˜ŊåœĻ朎äŧ“åš“å·ē视äļšå…ģé”Ūč·Ŋåū„įš„æĩ‹čŊ•åĨ—äŧķäķˆé™Ī CI 抖åŠĻ。无 CHANGELOG å…ģč”ïžˆįšŊæĩ‹čŊ•æ”đåŠĻäđŸäļéœ€čĶã€‚

å…ģ䚎å…ģ联 issue 需č́čŊŽäļ€į‚đ因äļšæŸĨ重įŽŊčŠ‚äžšįœ‹åŪƒïžš#15737 įŠķ态æ˜Ŋ closed as completedïžŒä―†äļæ˜Ŋį”ąå·ē合åđķįš„ PR å…ģé—­įš„ —— closer č§Ģ析įŧ“æžœäļš null。æ˜Ŋä― åœĻ垀čŋ™äļŠ PR 前å…Ŧåˆ†é’Ÿč‡Šå·ąå…ģįš„ïžŒį†į”ąæ˜Ŋ"æ­Ī后朊再åĪįŽ°"。我å·ēįĄŪčŪĪčŊĨæ”đåŠĻįĄŪåŪžäļåœĻ main9acb3a5a1d68aäļŠïžšé‚ĢäļŠį”Ļäū‹äŧį„ķæ˜ŊčĢļ调 runCleanup('local')withDeadPids äđŸåŠæœ‰åŽŸæœ‰įš„äļĪäļŠč°ƒį”Ļį‚đ。所äŧĨåۃäļæ˜Ŋå·ēč―åœ°äŋŪåĪįš„é‡åĪ PR我æēĄæœ‰å…ģ闭åŪƒã€‚äđ‹æ‰€äŧĨčĶæŠŠčŋ™äļŠæ‰‹åŠĻå…ģ闭į‚đ凚æĨæ˜Ŋ因äļščŊĨįŽŊčŠ‚æ— æģ•匚分"抖åŠĻįǰ巧æēĄå†å‡šįް"和"抖åŠĻå·ēčĒŦäŋŪåĨ―"â€”â€”č€ŒæœŽ PR äļŧåž å‰č€…åŒæ ·å€žåū—加囚čŋ™äļ€į‚đ我čŪĪ同æēĄæœ‰åĪįŽ°äļį­‰äšŽäŋŪåĪïžŒäļ‹äļ€å°įđåŋ™įš„ runner äŧåŊčƒ―æ’žäļŠ 626262。

规æĻĄïžš äļé€‚į”Ļ —— 朊č§Ķ及æ ļåŋƒč·Ŋåū„及有 packages/cli/src/commands/review/ äļ‹äļ€äļŠæ–‡äŧķäļ”į”Ÿäš§é€ŧčū‘čĄŒæ•°äļš 0因äļšå”Ŋä”đåŠĻįš„æ–‡äŧķæ˜Ŋ *.test.ts。äļæķ‰åŠ Tier 1/Tier 2äđŸäļč§Ķ发åΧ PR 提įĪšã€‚

æ–đæĄˆïžš æ”đåŠĻåūˆå°ïžŒäļ”äļŽį›ļé‚ŧį”Ļäū‹įš„做æģ•äļ€č‡ī。čŊĨäŧĢį å—é‡Œä―ŋį”Ļ字éĒ量 pid įš„į”Ļäū‹äļ­ïžŒ717171/727272 äļŽ 838383 å·ēįŧį”Ļ withDeadPids åŒ…äš†ïžŒč€Œ 515151 äļŽ 424242 æ˜Ŋ内联æĄĐ掉 process.kill因äļšåۃäŧŽéœ€č́į›ļåįš„æŽĒæīŧįŧ“æžœ —— 所äŧĨ 626262 įĄŪåŪžæ˜Ŋå”Ŋäļ€äļ€äļŠæēĄæœ‰äŧŧä―•å­˜æīŧ控åˆķįš„å­—éĒ量 pid į”Ļäū‹ã€‚我äđŸč€ƒč™‘čŋ‡åĶäĄč·ŊïžšåĪį”ĻčŊĨ块å·ēæœ‰įš„ deadPid先前å…ŦäļŠį”Ļäū‹äūčĩ–įš„ã€spawn 后å·ē回æ”ķįš„å­čŋ›įĻ‹ pid。é‚Ģæ · diff æ›īå°ïžŒä―†æ›īåžą —— åۃäŧį„ķæ˜ŊåŊđåŪŋäļŧ朚 pid åĪį”Ļįš„å‡čŪūïžŒč€Œäļæ˜Ŋæĩ‹čŊ•č‡Šå·ąįŧ™å‡šįš„äš‹åŪž —— 而äļ”äžšæ”đåŠĻ断čĻ€é‡Œįš„ fixture åå­—ã€‚ä― įš„åšæģ•æ›īåĨ―。

äļ€äļŠæčŋ°äļŠįš„小é—ŪéĒ˜ïžŒäļæ˜ŊäŧĢ᠁é—ŪéĒ˜ïžš"和同äļ€äŧĢį å—é‡Œå…ķåŪƒäš”äļŠå­Īå„ŋį”Ļäū‹įš„做æģ•äļ€č‡ī"把先äū‹čŊīčŋ‡åĪī乆 —— į”Ļ withDeadPids įš„æ˜ŊäļĪäļŠïžŒåĶåĪ–äļĪäļŠæ˜Ŋ把æŽĒ针æĄĐ成į›ļ反效果。åŧščŪŪæ”đäļ€äŽŠčūžïžŒå…åū—后æĨįš„čŊŧ者åŽŧæ‰ūé‚Ģ"乔äļŠ"。

éĢŽé™Đïžš æ— å‡įš§éĢŽé™ĐäŋĄå· —— æœŠå‘―äļ­éŦ˜éĢŽé™Đč·Ŋåū„äļ”čŊĨ文äŧķæœŽčšŦ按æĩ‹čŊ•æ–‡äŧķčĒŦ排é™Ī。

čŋ›å…ĨäŧĢ᠁åŪĄæŸĨ 🔍

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

Reviewed at 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Code review

I read the diff against main at 9acb3a5a1d68a and against the production sweep it exercises. No critical blockers. What I checked, since a one-line test change is easy to wave through:

  • The stub is correctly scoped. process.kill appears exactly once in the production sweep — cleanup.ts:679, process.kill(Number(m[1]), 0), the liveness probe. So pinning it cannot alter any other half of runCleanup; the reap itself goes through the mocked execFileSync.
  • .not.toThrow() still pins what it pinned. An exception out of runCleanup propagates through withDeadPids's try/finally (whose finally only restores process.kill) and out to the expect, so the case still asserts that the EBUSY unlink is swallowed by the guard's catch {}. Wrapping did not soften the assertion.
  • The leak guard is intact. The case's own finally { mocks.rmSync.mockReset(); } is untouched, so the throwing rmSync still does not bleed into the six orphan cases that follow.
  • Scope resolution is fine. withDeadPids is a hoisted function declaration inside the same describe.skipIf(process.platform === 'win32') callback (declared at 1456, called at 1566), so the new call site sees it — and win32 skips the block entirely, matching the PR's own note.
  • The new comment's factual claim holds. Of the six literal-pid fixtures in the block (717171, 727272, 838383, 515151, 626262, 424242), 626262 was the only one with no liveness control: two wrap in withDeadPids, two stub process.kill inline for the opposite (alive) answer, and process.pid is deliberately live. The wording scopes itself to "hardcoded pid", so it does not misdescribe the eight deadPid-based fixtures either.

One optional suggestion, not a blocker. The added comment (1560-1564) restates the helper's own docstring at 1451-1455, which already says a literal pid can be alive on a busy host and the test would then "fail for a reason that has nothing to do with what it pins". The case also already carries its mutant note above (the catch {} explanation), so this is now nine comment lines over a two-line body, and the two pre-existing withDeadPids call sites (1479, 1509) carry no equivalent. The half that does earn its place is the misdiagnosis risk — that a liveness flake here reads exactly like the unlink guard having been removed. If you want it tighter, keep that sentence and drop the rest; equally fine to leave as is.

Independent-proposal comparison, for the record: my alternative was to swap 626262 for the block's existing deadPid (a spawned-then-reaped child pid, which eight earlier cases already trust), needing no wrapper at all. It is a smaller diff but a weaker guarantee — still a host assumption about pid reuse rather than a fact of the test — and it churns the fixture name inside the assertion. Your approach beats mine and matches the nearer neighbors.

Testing evidence

This is an unattended CI run, so I did not build or execute anything from this PR; the evidence below is the PR's own CI, read through the API for commit 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9.

The honest headline: the one lane that matters is still running. Test (ubuntu-latest, Node 22.x) is in_progress, and it is the only Test job that will execute this case — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) are both skipped on this commit. That is also the lane that produced the original flake. Lint & Static (ubuntu-latest, Node 22.x) passed, which covers typecheck and lint for the changed file.

CI results for 9aadf13 — this table auto-updates as CI workflows complete:

Check Conclusion
Installed npm Node26 private Bun acceptance âģ running
TUI parity snapshots (ink vs opentui) ❌ failure
Check TUI impact ✅ success
Classify PR ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
Managed Agent gate ✅ success
OpenTUI no-flicker gate ✅ success
Select the Managed Agent tier ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / æŊäļŠæĢ€æŸĨ名äļ€čĄŒïžˆå–最新äļ€æŽĄčŋčĄŒïž‰ïžŒįœį•Ĩ skippedåĪąčīĨéĄđ排åœĻ最前。

triage and review-pr are also in flight on this commit; those are bot orchestration jobs, not this PR's CI, so they should not be read as product signal either way. No check is red, so there is no failing-job excerpt to quote. I fetched once and did not poll — this repo's unit suite runs long, and the finalize workflow rewrites the table above in place once CI settles.

Not verified: that the flake is actually gone. Worth being blunt about why this matters more than it looks. A green Test (ubuntu-latestâ€Ķ) run does not substantiate this fix, because the failure only manifests when the host happens to have pid 626262 resident. On a quiet runner the case passes identically with and without the diff — so green CI is consistent with both "fixed" and "got lucky again", and cannot distinguish them. That is precisely the shape #15737 was closed on ("has not recurred since").

The before/after in the PR description — renaming the fixture to a resident pid, dropping the wrapper, seeing "Nothing to clean for target \"local\".", then restoring it — is the right experiment and would settle this. But it is the author's reported local result on macOS, not something I re-ran, and I am not presenting it as evidence. What I did independently confirm is the historical half: the archived log for job 122343675142 shows the failure reproducing on all three attempts with exactly that assertion diff at cleanup.test.ts:1562, on a self-hosted runner reporting load[6.76 15.45 18.25] and hosttests[17].

Sandboxed verification would settle the remaining half: @qwen-code /verify — the load-bearing claim is that pinning the probe (not a quiet runner) is what makes this case deterministic, and that is exactly what an A/B against the base build with the fixture pid forced resident would prove. A green suite cannot, since it passes with the wrapper removed whenever 626262 is free. There is no TUI surface here, so /tmux has nothing to add.

äļ­æ–‡čŊŽ

äŧĢ᠁åŪĄæŸĨ

我把 diff åŊđį€ main9acb3a5a1d68aäŧĨ及åŪƒæ‰€æĩ‹čŊ•įš„į”Ÿä𧿉Ŧ描é€ŧčū‘äļ€čĩ·čŊŧ䚆。æēĄæœ‰é˜ŧ断性é—ŪéĒ˜ã€‚ äļ€čĄŒįš„æĩ‹čŊ•æ”đåŠĻåūˆåŪđæ˜“čĒŦéĄšæ‰‹æ”ūčŋ‡ïžŒæ‰€äŧĨæˆ‘é€æĄæ ļ乆äļ‹éĒčŋ™äš›ïžš

  • æĄĐįš„ä―œį”Ļ范å›īæ˜ŊåŊđįš„ã€‚ į”Ÿäš§æ‰Ŧ描里 process.kill åŠå‡šįŽ°äļ€æŽĄ —— cleanup.ts:679 įš„ process.kill(Number(m[1]), 0)åģ存æīŧæŽĒ针。因æ­Ī囚åۚåۃäļäžšå―ąå“ runCleanup įš„äŧŧä―•å…ķåۃįŽŊčŠ‚ïž›įœŸæ­Ģįš„ reap čĩ°įš„æ˜Ŋå·ēčĒŦ mock įš„ execFileSync。
  • .not.toThrow() åŪˆįš„äļœčĨŋæēĄæœ‰å˜åžąã€‚ runCleanup æŠ›å‡šįš„åž‚åļļ侚įĐŋčŋ‡ withDeadPids įš„ try/finallyå…ķ finally 及čīŸčīĢčŋ˜åŽŸ process.kill䞠到 expect所äŧĨčŊĨį”Ļäū‹äūį„ķåœĻ断čĻ€ EBUSY įš„ unlink čĒŦå…œåš•įš„ catch {} 吞掉。包äļ€åą‚åđķæēĄæœ‰æ”ūæū断čĻ€ã€‚
  • é˜ēæģ„枏é€ŧčū‘åیåĨ―。 į”Ļäū‹č‡Šå·ąįš„ finally { mocks.rmSync.mockReset(); } 朊čĒŦæ”đåŠĻ因æ­Ī䞚抛垂åļļįš„ rmSync äŧį„ķäļäžšæļ—到后éĒįš„å…­äļŠå­Īå„ŋį”Ļäū‹é‡Œã€‚
  • ä―œį”Ļ域æēĄé—ŪéĒ˜ã€‚ withDeadPids æ˜Ŋ同äļ€äļŠ describe.skipIf(process.platform === 'win32') å›žč°ƒé‡ŒčĒŦæå‡įš„å‡―æ•°åĢ°æ˜Žïžˆ1456 行åĢ°æ˜ŽïžŒ1566 行调į”Ļïž‰ïžŒæ–°č°ƒį”Ļį‚đčƒ―įœ‹åˆ°åŪƒïž›win32 侚æ•ī块č·ģčŋ‡ïžŒäļŽ PR č‡Šå·ąįš„čŊŽäļ€č‡ī。
  • 新æģĻé‡Šé‡Œįš„äš‹åŪžæ€§čŊīæģ•成įŦ‹ã€‚ čŊĨ块六äļŠå­—éĒ量 pid fixture717171、727272、838383、515151、626262、424242äļ­ïžŒåŠæœ‰ 626262 æēĄæœ‰äŧŧä―•å­˜æīŧ控åˆķïžšäļĪäļŠį”Ļ withDeadPids 包äļĪäļŠå†…联æĄĐ掉 process.kill äŧĨåū—到į›ļåįš„ïžˆå­˜æīŧįŧ“果process.pid 则æ˜Ŋ故意存æīŧįš„ã€‚æģĻé‡ŠæŠŠčŒƒå›ī限åۚåœĻ"写æ­ŧįš„ pid"因æ­ĪäđŸæēĄæœ‰é”™čŋ°é‚Ģå…ŦäļŠåŸšäšŽ deadPid įš„ fixture。

äļ€äļŠåŊ选åŧščŪŪäļæ˜Ŋé˜ŧ断éĄđ。 新åĒžæģĻ释1560-1564 čĄŒïž‰é‡åĪäš† 1451-1455 行 helper č‡Šå·ąįš„ docstring —— åŽč€…å·ēįŧčŊŽå­—éĒ量 pid åœĻįđåŋ™åŪŋäļŧ朚äļŠåŊčƒ―å­˜æīŧ䚎æ˜Ŋæĩ‹čŊ•äžš"因äļšäļŽåۃč́åŪˆįš„äļœčĨŋ无å…ģįš„åŽŸå› åĪąčīĨ"。čŊĨį”Ļäū‹ä–đäđŸå·ēįŧæœ‰č‡Šå·ąįš„å˜åž‚ä―“æģĻ释č§Ģ释 catch {}所äŧĨįŽ°åœĻæ˜ŊäđčĄŒæģĻé‡ŠåŽ‹į€äļĪ行äŧĢį ïžŒč€ŒäļĪäļŠæ—Ēæœ‰įš„ withDeadPids 调į”Ļį‚đ1479、1509 čĄŒïž‰éƒ―æēĄæœ‰įąŧäžžæģĻé‡Šã€‚įœŸæ­Ģ有äŧ·å€žįš„é‚Ģäļ€åŠæ˜Ŋ"čŊŊčŊŠéĢŽé™Đ"—— čŋ™é‡Œįš„å­˜æīŧ抖åŠĻįœ‹čĩ·æĨ和 unlink 兜嚕čĒŦ删掉äļ€æĻĄä ·ã€‚åĶ‚æžœæƒģæ›īįī§å‡‘äŋį•™é‚Ģäļ€åĨ、åŽŧ掉å…ķä―™åģåŊ原样äŋį•™äđŸæēĄé—ŪéĒ˜ã€‚

čŪ°å―•äļ€äˆ‘įš„į‹ŽįŦ‹æ–đæĄˆåŊđæŊ”我原朎æƒģįš„æ˜Ŋ把 626262 æĒ成čŊĨ块å·ēæœ‰įš„ deadPidspawn 后å·ē回æ”ķįš„å­čŋ›įĻ‹ pid前éĒå…ŦäļŠį”Ļäū‹å·ēįŧäūčĩ–åŪƒïž‰ïžŒčŋ™æ ·åیå…Ļäļéœ€č́ wrapper。diff æ›īå°ïžŒä―†äŋčŊæ›īåžą —— åۃäŧį„ķæ˜ŊåŊđåŪŋäļŧ朚 pid åĪį”Ļįš„å‡čŪūïžŒč€Œäļæ˜Ŋæĩ‹čŊ•č‡Šå·ąįŧ™å‡šįš„äš‹åŪž —— 而äļ”äžšæ”đåŠĻ断čĻ€é‡Œįš„ fixture åå­—ã€‚ä― įš„åšæģ•æŊ”æˆ‘įš„æ›īåĨ―äđŸå’Œæ›īčŋ‘įš„é‚ŧåą…äļ€č‡ī。

æĩ‹čŊ•čŊæŪ

čŋ™æ˜Ŋäļ€æŽĄæ— äššå€žåŪˆįš„ CI čŋčĄŒïžŒå› æ­Ī我æēĄæœ‰æž„åŧšæˆ–æ‰§čĄŒæœŽ PR įš„äŧŧä―•äŧĢį ïž›äļ‹éĒįš„čŊæŪæ˜Ŋ通čŋ‡ API čŊŧå–įš„ã€æœŽ PR č‡Šå·ąåœĻ commit 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9 äļŠįš„ CI įŧ“果。

į›īčŊīįŧ“čŪšïžšå”Ŋäļ€å…ģé”Ūįš„é‚ĢæĄ lane čŋ˜åœĻč·‘ã€‚ Test (ubuntu-latest, Node 22.x) įŠķ态æ˜Ŋ in_progressïžŒč€Œåۃæ˜Ŋå”Ŋäļ€äžšæ‰§čĄŒčŊĨį”Ļäū‹įš„ Test job —— 朎 commit äļŠ Test (macos-latest, Node 22.x) äļŽ Test (windows-latest, Node 22.x) éƒ―æ˜Ŋ skipped。čŋ™äđŸæ­Ģæ˜Ŋå―“åˆäš§į”ŸæŠ–åŠĻįš„é‚ĢæĄ lane。Lint & Static (ubuntu-latest, Node 22.x) å·ē通čŋ‡ïžŒčĶ†į›–äš†æ”đåŠĻ文äŧķįš„įąŧ型æĢ€æŸĨäļŽ lint。

CI įŧ“æžœčĄĻ见ä–đč‹ąæ–‡éƒĻåˆ†įš„æ ‡čŪ°åŒšåŸŸïžŒį”ą finalize å·Ĩä―œæĩåœĻ CI č·‘åŪŒåŽåŽŸåœ°æ›ī新。

triage äļŽ review-pr åœĻčŊĨ commit äļŠäđŸčŋ˜åœĻč·‘ïž›åۃäŧŽæ˜Ŋ bot įž–æŽ’ jobäļæ˜Ŋ朎 PR įš„ CI无čŪšįŧ“æžœåĶ‚ä―•éƒ―äļåš”å―“ä―œäš§å“äŋĄå·ã€‚į›Ū前æēĄæœ‰įšĒč‰ē check所äŧĨæēĄæœ‰åĪąčīĨæ—Ĩåŋ—ቇæŪĩåŊ垕。我及取䚆äļ€æŽĄįŧ“果æēĄæœ‰č―ŪčŊĒ â€”â€” 朎äŧ“åš“įš„å•æĩ‹åĨ—äŧķ耗æ—ķåūˆé•ŋčĄĻæ žäžšį”ą finalize å·Ĩä―œæĩåœĻ CI įĻģåŪšåŽå°ąåœ°é‡å†™ã€‚

朊銌čŊïžšæŠ–åŠĻæ˜ŊåĶįœŸįš„æķˆåĪąäš†ã€‚ čŋ™äļ€į‚đ倞åū—čŪē透因äļšåۃæŊ”įœ‹äļŠåŽŧæ›ī重čĶã€‚Test (ubuntu-latestâ€Ķ) 变įŧŋåđķäļčƒ―čŊæ˜Žčŋ™äļŠäŋŪåĪæœ‰æ•ˆïžŒå› äļščŊĨåĪąčīĨ及åœĻåŪŋäļŧ朚恰åĨ―有 pid 626262 存æīŧæ—ķæ‰å‡šįŽ°ã€‚åœĻåŪ‰é™įš„ runner äļŠïžŒæœ‰æēĄæœ‰čŋ™äļŠ diff į”Ļäū‹éƒ―ä ·é€ščŋ‡ —— 所äŧĨįŧŋč‰ē CI 同æ—ķå…žåŪđ"äŋŪåĨ―乆"和"čŋ™æŽĄåˆčĩ°čŋäš†"无æģ•åŒšåˆ†äšŒč€…ã€‚č€Œčŋ™æ°æ°å°ąæ˜Ŋ #15737 čĒŦå…ģ闭æ—ķ所äūæŪįš„å―Ēæ€ïžˆ"æ­Ī后朊再åĪįŽ°"。

PR 描čŋ°é‡Œįš„ before/after —— 把 fixture æĒ成äļ€äļŠåŋ…į„ķ存æīŧįš„ pid、åŽŧ掉 wrapperã€įœ‹åˆ° "Nothing to clean for target \"local\"."、再加回æĨ —— æ˜Ŋæ­ĢįĄŪįš„åŪžéŠŒïžŒäđŸįĄŪåŪžčƒ―åۚčŪšã€‚ä―†é‚Ģæ˜Ŋä―œč€…åœĻ macOS äļŠč‡ŠæŠĨįš„æœŽåœ°įŧ“果äļæ˜Ŋæˆ‘é‡č·‘įš„ïžŒæˆ‘äļæŠŠåŪƒå―“ä―œčŊæŪå‘ˆįŽ°ã€‚æˆ‘į‹ŽįŦ‹įĄŪčŪĪįš„æ˜Ŋ历åēæ€§įš„äļ€åŠïžšjob 122343675142 įš„å―’æĄĢæ—Ĩåŋ—æ˜ūįĪščŊĨåĪąčīĨåœĻäļ‰æŽĄå°čŊ•äļ­å…ĻéƒĻåĪįŽ°ïžŒæ–­čĻ€ diff äļŽæčŋ°äļ€č‡īïžŒä―į―Ū cleanup.test.ts:1562äļ”å―“æ—ķæ˜Ŋäļ€å°č‡Šæ‰˜įŪĄ runneræŠĨ告 load[6.76 15.45 18.25]、hosttests[17]。

æē™įŪąéŠŒčŊåŊäŧĨåۚäļ‹å‰Đäļ‹įš„äļ€åŠïžš@qwen-code /verify —— ᜟæ­Ģåƒé‡įš„čŊīæģ•æ˜Ŋ"čŪĐčŊĨį”Ļäū‹å˜åū—įĄŪåŪšæ€§įš„æ˜Ŋ囚åۚæŽĒé’ˆïžŒč€Œäļæ˜Ŋ runner 恰åĨ―åŪ‰é™"ïžŒč€Œčŋ™æ­Ģæ˜Ŋæ‹ŋ base 构åŧšåš A/B、åđķæŠŠ fixture pid åžšåˆķčŪūäļšå­˜æīŧæ‰€čƒ―čŊæ˜Žįš„。įŧŋč‰ēåĨ—äŧķčŊæ˜Žäļäš†ïžŒå› äļšåŠč́ 626262 įĐšé—ēåŽŧ掉 wrapper äđŸį…§æ ·é€ščŋ‡ã€‚čŋ™é‡ŒæēĄæœ‰ TUI į•ŒéĒ所äŧĨ /tmux åļŪäļäļŠåŋ™ã€‚

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

Reviewed at 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9 · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — the change is correct, minimal, and removes a real host assumption; the only nits are a redundant comment block and an overstated "five siblings" in the description, neither of which is worth holding the PR for.

Stepping back: this is a six-line test-only change that makes one case deterministic, and it does it the way the neighboring cases in the same block already do it. I went looking for a reason to say no and did not find one that survives contact with the evidence.

The thing I want to be explicit about, because it is the only real judgment call here, is whether this PR is needed at all. #15737 was closed on the grounds that the signature had not recurred, and the product code was never wrong — the sweep correctly declined to reap a socket whose pid looked alive. So one could argue this is hygiene. I don't think that reading holds. The failure did land on main CI, it failed all three retries, and its output (Nothing to clean for target "local") is actively misleading: it reads exactly like the unlink guard having been deleted, which is the one thing the case exists to pin. A test that can red-line main and point the investigator at the wrong code is a defect in the suite, not a cosmetic preference — and the fix costs nothing in product risk. Non-recurrence over a few pushes is luck on a quiet runner, not a fix.

On my own independent proposal: I'd have reached for the block's existing deadPid instead of the wrapper. Having read the code closely, the PR's choice is better than mine — deadPid still leans on the host not reusing a pid, whereas stubbing the probe makes "this launcher is dead" a fact of the test. I'm recording that I changed my mind rather than quietly adopting the PR's framing.

What I would not claim: that the flake is now provably gone. Green CI cannot show it, since the case passes with the wrapper removed whenever 626262 happens to be free — the suite is green either way on a quiet runner. That is a property of flake fixes generally, not a defect in this one, and the change is strictly monotonic: it can only remove a failure mode, never add one. The /verify A/B named in my Stage 2 comment is the way to close that gap if anyone wants it closed; I don't think it should block the merge.

In six months I would thank the author for this, not curse them: the next person to see this case fail will now be looking at the unlink guard, which is the correct place to look.

Approval deferred until CI lands green on 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9. Two of this PR's own workflow runs are still in flight — Qwen Code CI (which carries Test (ubuntu-latest, Node 22.x), the only lane that executes this case) and tui-parity. Approving now would attest to a result that does not exist yet, so I am posting no review in this run; the finalize workflow will place the same commit-pinned approval once every check on that commit completes green, and will withhold it if anything lands red or the head moves.

äļ­æ–‡čŊŽ

Confidence: 4/5 —— æ”đåŠĻæ­ĢįĄŪ、极小åđķäļ”įœŸįš„åŽŧ掉䚆äļ€äļŠåŊđåŪŋäļŧæœšįš„å‡čŪūïž›äŧ…æœ‰įš„äļĪäļŠå°é—Ūéǘæ˜ŊæģĻé‡Šå†—ä―™ã€äŧĨ及描čŋ°é‡Œ"乔äļŠåŒįš§į”Ļäū‹"čŊīčŋ‡äš†åĪīïžŒéƒ―äļå€žåū—äļšæ­ĪåĄä―čŋ™äļŠ PR。

退ä­Ĩįœ‹æ•īä―“ïžščŋ™æ˜Ŋäļ€äļŠå…­čĄŒįš„įšŊæĩ‹čŊ•æ”đåŠĻčŪĐäļ€äļŠį”Ļäū‹å˜åū—įĄŪåŪšæ€§ïžŒč€Œäļ”į”Ļįš„å°ąæ˜Ŋ同äļ€äŧĢį å—é‡Œį›ļé‚ŧį”Ļäū‹å·ēæœ‰įš„åšæģ•。我į‰đ意åŽŧæ‰ūåĶå†ģåŪƒįš„į†į”ąïžŒæēĄæœ‰æ‰ū到äŧŧä―•įŧåū—čĩ·čŊæŪæĢ€éŠŒįš„ã€‚

我æƒģ把čŋ™é‡Œå”Ŋäļ€įš„åˆĪ断éšūį‚đčŪēæļ…æĨšïžŒå°ąæ˜Ŋčŋ™äļŠ PR 到嚕需äļéœ€čĶã€‚#15737 æ˜ŊäŧĨ"čŊĨį­ū名æ­Ī后朊再åĪįŽ°"äļšį”ąå…ģé—­įš„ïžŒč€Œäļ”䚧品äŧĢ᠁äŧŽåĪī到å°ūæēĄæœ‰é”™ —— æ‰Ŧ描æ­ĢįĄŪ地拒įŧåŽŧ reap äļ€äļŠ pid įœ‹čĩ·æĨčŋ˜å­˜æīŧįš„ socket。所äŧĨåŊäŧĨčŊīčŋ™åŠæ˜ŊäŧĢ᠁åŦį”Ÿã€‚æˆ‘äļčŪĪ同čŋ™äļŠčŊŧæģ•。čŊĨåĪąčīĨįĄŪåŪžč―åœĻ乆 main CI äļŠïžŒäļ‰æŽĄé‡čŊ•å…ĻåĪąčīĨïžŒč€Œäļ”åŪƒįš„čū“凚Nothing to clean for target "local"有åŪžčīĻčŊŊåŊžæ€§ïžšåŪƒįœ‹čĩ·æĨ和 unlink 兜嚕čĒŦ删掉äļ€æĻĄä ·ïžŒč€Œé‚Ģæ­Ģæ˜ŊčŊĨį”Ļäū‹č́åŪˆįš„å”Ŋäļ€äļœčĨŋ。äļ€äļŠčƒ―把 main åž„įšĒ、åđķæŠŠæŽ’æŸĨč€…æŒ‡å‘é”™čŊŊäŧĢį įš„æĩ‹čŊ•æ˜ŊåĨ—äŧķæœŽčšŦįš„įžšé™·ïžŒäļæ˜ŊåŪĄįūŽååĨ― —— 而čŋ™äļŠäŋŪåĪåœĻ䚧品éĢŽé™ĐäļŠįš„äŧĢäŧ·æ˜Ŋé›ķã€‚å‡ æŽĄ push 内æēĄæœ‰åĪįŽ°ïžŒåŠæ˜ŊåŪ‰é™ runner äļŠįš„čŋæ°”äļæ˜ŊäŋŪåĪã€‚

å…ģäšŽæˆ‘č‡Šå·ąįš„į‹ŽįŦ‹æ–đæĄˆïžšæˆ‘åŽŸæœŽäžšåŽŧį”ĻčŊĨ块å·ēæœ‰įš„ deadPidïžŒč€Œäļæ˜Ŋčŋ™äļŠ wrapper。äŧ”įŧ†čŊŧåیäŧĢį åŽïžŒæˆ‘čŪĪäļš PR įš„é€‰æ‹ĐæŊ”æˆ‘įš„æ›īåĨ― —— deadPid äŧį„ķäūčĩ–åŪŋäļŧ朚äļåĪį”ĻčŊĨ pidïžŒč€ŒæĄĐ掉æŽĒ针则čŪĐ"čŋ™äļŠåŊåŠĻå™Ļå·ēįŧæ­ŧ乆"成äļšæĩ‹čŊ•č‡Šå·ąįŧ™å‡šįš„äš‹åŪžã€‚æˆ‘æŠŠ"我æ”đå˜äš†įœ‹æģ•"čŪ°å―•äĨïžŒč€Œäļæ˜Ŋ悄悄采įšģ PR įš„æĄ†æžķå―“ä―œč‡Šå·ąįš„įŧ“čŪšã€‚

我äļäžšäļŧåž įš„æ˜Ŋ抖åŠĻįŽ°åœĻå·ēčĒŦčŊæ˜ŽæķˆåĪąã€‚įŧŋč‰ē CI čŊæ˜Žäļäš†čŋ™äļ€į‚đ因äļšåŠč́ 626262 恰åĨ―įĐšé—ēåŽŧ掉 wrapper į”Ļäū‹äđŸä ·é€ščŋ‡ —— åœĻåŪ‰é™įš„ runner äļŠäļĪį§å†™æģ•éƒ―æ˜Ŋįŧŋįš„ã€‚čŋ™æ˜Ŋ所有抖åŠĻäŋŪåĪįš„å…ąæ€§ïžŒäļæ˜Ŋčŋ™äļŠäŋŪåĪįš„įžšé™·ïžŒč€Œäļ”čŊĨæ”đåŠĻæ˜ŊäļĨæ žå•č°ƒįš„ïžšåŪƒåŠåŊčƒ―į§ŧé™Īäļ€į§åĪąčīĨæĻĄåžïžŒäļåŊčƒ―æ–°åĒžã€‚åĶ‚æžœįĄŪåŪžčĶæŠŠčŋ™äļŠįžšåĢčĄĨäļŠïžŒæˆ‘åœĻ Stage 2 čŊ„čŪšé‡Œį‚đåįš„ /verify A/B å°ąæ˜Ŋ办æģ•ïž›ä―†æˆ‘čŪĪäļšåۃäļåš”čŊĨé˜ŧåĄžåˆåđķ。

六äļŠæœˆåŽæˆ‘䞚感č°Ēä―œč€…ïžŒč€Œäļæ˜Ŋ驂äŧ–ïžšäļ‹äļ€äļŠįœ‹åˆ°čŋ™äļŠį”Ļäū‹åĪąčīĨįš„äššïžŒäžšåŽŧæŸĨ unlink 兜嚕 —— 而é‚Ģæ­Ģæ˜Ŋåš”čŊĨæŸĨįš„åœ°æ–đ。

æ‰đ准将åŧķčŋŸåˆ° CI åœĻ 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9 äļŠå…Ļįŧŋäđ‹åŽã€‚ 朎 PR č‡Šå·ąčŋ˜æœ‰äļĪäļŠ workflow run åœĻč·‘ —— Qwen Code CIå…ķäļ­åŒ…åŦ Test (ubuntu-latest, Node 22.x)åģå”Ŋäļ€äžšæ‰§čĄŒčŊĨį”Ļäū‹įš„ lane和 tui-parity。æ­Īåˆŧæ‰đå‡†į­‰äšŽäļšäļ€äļŠå°šäļå­˜åœĻįš„įŧ“æžœčƒŒäđĶ所äŧĨæœŽæŽĄčŋčĄŒäļæäšĪäŧŧä―• reviewïž›finalize å·Ĩä―œæĩäžšåœĻčŊĨ commit ä‰€æœ‰ check å…ĻįŧŋåŽč―äļ‹åŒäļ€äļŠįŧ‘åۚ commit įš„æ‰đ准č‹Ĩå‡šįŽ°įšĒč‰ē或 head å‘į”Ÿį§ŧåŠĻ则äļäšˆæ‰đ准åŧķčŋŸæ‰đ准标čŪ°å·ē附åœĻ朎čŊ„čŪšäļ­ïž‰ã€‚

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

Reviewed at 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9 · re-run with @qwen-code /triage

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Updated both language versions of the description: two sibling cases use the helper, not five. The patch remains at 9aadf13efcea13b051c3dfc2fd6cd34c97705ef9; no code change was needed for the current feedback. The existing comment explains the fixture's non-obvious liveness premise, so I left that optional wording suggestion unchanged. #15737 is reopened because this fix has not merged.

The current Ubuntu unit, lint/static, no-AK integration and web-shell smoke jobs passed. The TUI parity job remains red, and installed-npm acceptance and automated review were queued in this snapshot. This is not an all-green or merge-ready report.

I inspected the failed TUI job and its tui-migration-9aadf13efcea13b051c3dfc2fd6cd34c97705ef9-attempt-1 artifact. In shell-focus-bun-opentui-K78JJ2, the assertion at line 564 saw the byte stream without the final 09, but CHILD-EVENTS.json records that byte at 1793156790084, 431 ms after the Shift+Tab entry in KEYS.json, before the child exited normally. The test waits for the footer's approval-mode update and then immediately reads the child log; the footer update does not acknowledge child receipt. This supports delayed observation for this failed run, rather than a permanently dropped byte. The parity job runs a separate integration suite and CLI process; it does not execute the cleanup unit case or its temporary process.kill stub.

That needs a separate focused test repair. The original #15712 job actually failed at line 543 because its frame lacked DRAFT_OWNED, not at the missing-tab assertion. A fix for the latter alone would not establish that #15712 is resolved. No further rerun was attempted after the earlier reported workflow-file rerun rejection.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

The one red check here (TUI parity snapshots) is not this PR's.

integration-tests/interactive/tui-foreground-shell.test.ts > keeps PTY bytes and composer drafts with their actual input owner fails at :564 — expect(c.bytes()).toBe(shiftedInput) — expecting the trailing TAB (â€Ķ494e4e45525f4209) and receiving the buffer one byte short (â€Ķ494e4e45525f42). This PR only changes packages/cli/src/commands/review/cleanup.test.ts, and the failing test file is byte-identical to main (5d64af81e3), so nothing here produced it.

Root cause is in the test: that case has three byte assertions, and two of them (resumed input at :549, paste at :571) first wait for c.bytes().length to reach the expected length. The Shift+Tab assertion is the only one without that guard — it runs immediately after the frame shows plan mode, while the PTY write can still be in flight.

#15763 adds exactly that missing guard. Once it lands, this check should stop failing here for a reason that has nothing to do with this diff. The rest of CI on this head is green.

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE @ ff86f8885f2f344b5bf3caa50adcea2051729fc3čŊ„åŪĄéĒå†ŧįŧ“ïžšbase 78c210441 文äŧķ +6/−1įšŊæĩ‹čŊ•。

įŧ“čŪšïžšæ—  P0–P2 å‘įŽ°ã€‚čŋ™äļŠį”Ļäū‹åŽŸå…ˆįĄŪåŪžæŠžæģĻåœĻåŪŋäļŧ pid čĄĻäļŠïžŒé’‰æ­ŧæŽĒ针æ˜Ŋæ­ĢįĄŪäŋŪæģ•。

æ ļčŋ‡įš„į‚đčŊŧ head å…Ļ文äļŽ cleanup.tsäļæ˜ŊåŠįœ‹ hunk

  1. čĒŦé’‰įš„æ˜Ŋå”Ŋäļ€įš„æžį―‘į‚đ。 čŋ™äļŠ describe é‡Œæ‰€æœ‰įĄŽįž–į  pid įš„åĪđå…·ïžš717171/727272:1479 į”Ļ withDeadPids、838383:1509 同、515151:1533-1540 æ˜ū垏 EINVAL stub、424242:1618-1625 æ˜ū垏 EPERM stub及有 626262:1552æ­Ī前į›īæŽĨč·‘ runCleanup —— įĄŪåŪžæ˜Ŋ最后äļ€äļŠæēĄé’‰įš„。å…ķä―™åĪđ具į”Ļįš„æ˜Ŋ spawnSync(...).pid:866-867čŋ™į§čŋčĄŒæœŸåŋ…į„ķå·ēæ­ŧįš„ pidäļåąžåŒįąŧéĢŽé™Đ。
  2. stub čĶ†į›–įš„æ˜ŊæŽĒ针而äļæ˜Ŋ杀䚚č·Ŋåū„。 runCleanup 里 process.kill åŠå‡šįŽ°åœĻ liveness æŽĒæĩ‹ïžˆcleanup.ts:679ESRCH åˆĪæ­ŧ、å…ķä―™åˆĪæīŧåŪžé™…æļ…į†į”ą execFileSync čĩ°ïžˆæœŽæĩ‹čŊ•å·ē mock。所äŧĨ把 process.kill æĒ成恒抛 ESRCH äļäžšæŽĐį›–äŧŧä―•įœŸåŪžčĄŒäļšã€‚
  3. čŋ˜åŽŸæ˜Ŋ finally įš„ïžšwithDeadPids:1456-1466åœĻ try/finally 里æĒåĪåŽŸå‡―æ•°ïžŒåž‚åļļč·Ŋåū„äđŸäļäžšæŠŠ process.kill æģ„枏įŧ™åŽįŧ­į”Ļäū‹ã€‚
  4. 断čĻ€éĒæēĄåŠĻïžšnot.toThrow()、Reaped orphaned capture server é‚ĢčĄŒã€äŧĨ及 rmSync 抛 EBUSY įš„čŪūåŪšéƒ―åŽŸæ ·ïž›æ”đįš„åŠæ˜Ŋ「čŋ™äļŠ launcher æ˜Ŋæ­ŧįš„ã€čŋ™äļ€å‰į―Ū之åŪžäŧŽå‡čŪū变成䚋åŪžã€‚
  5. åĪąčīĨå―Ēæ€æ˜ŊįœŸįš„čŊŧčĩ·æĨ像čĒŦæĩ‹åŊđčąĄåäš†ïžšåĪđ具æīŧį€æ—ķ sweep 侚čū“凚 Nothing to clean for target "local"äļŽã€Œunlink åۈåŦčĒŦ删掉」同å―Ē â€”â€” æ­Ģ文čŋ™æĄå―’因我čŪĪ同。

CI åœĻ head äļŠåŋ…éĄŧčŊīæļ…æĨšïžšTest (ubuntu-latest, Node 22.x) äļŽ web-shell E2E Smoke (ubuntu-latest, Node 22.x) äļĪæĄéƒ―æ˜Ŋ cancelledäļæ˜Ŋį”Ļäū‹åˆĪįšĒ —— äļĪæĄįš„æģĻč§Ģéƒ―æ˜Ŋ The operation was canceled.äļ” 07:47:46Z čĩ·č·‘、08:17:43Z / 08:18:40Z įŧ“束æ­ĢåĨ―撞äļŠ ~30 分钟äļŠé™ã€‚čŋ™äļĪæĄäļæšåļĶäŧŧä―•æŒ‡å‘æœŽ diff įš„äŋĄå·ïžŒä―†äđŸæēĄæœ‰įŧ™å‡šįŧŋ合åđķ前需čĶé‡č·‘åۃäŧŽïž›å…ķä―™ 11 success / 4 skippedInstalled npm Node26 private Bun acceptance äŧ queued。

朊æ ļ朎朚æēĄæœ‰čŊĨåĨ—äŧķįš„æž„åŧšïžŒ109 tests passed 我æēĄæœ‰åĪįŽ°ïž›Windows lane 按 describe.skipIf(win32) æœŽå°ąäļč·‘čŋ™äļŠį”Ļäū‹ã€‚

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Status on the trail to approval:

  • The review bot APPROVED this head at 2026-10-28 08:41 after reading the whole file, not just the hunk — qwen-code-review-bot review on ff86f8885f2f344b5bf3caa50adcea2051729fc3, verdict: no P0-P2 findings, and it verified that this fixture is the only pid-table fixture in the describe that still leaks the host pid namespace.
  • TUI parity no longer fails on this head, because PR fix(test): wait for focused shell tab input before asserting bytes #15763 merged the world it depended on.
  • Current CI on the head: 0 failing, Lint & Static SUCCESS.
  • What is missing is the second human approval — the main ruleset counts human reviewers only, so reviewDecision still reads REVIEW_REQUIRED.

Test-only change, +6/−1. If you are a human reviewer looking at this PR for the first time, the earlier review comment on this thread is your fault-check entry point for the specific reasoning.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 918245eab316cb42f4657a4dae9dfcc456302c1a.

One test file, +6/−1. For a change that stubs a system call inside a test, the only question that matters is whether the stub hides part of the behaviour the test exists to pin — a liveness stub that also masked the kill path would turn this case green while testing nothing. So I read the production call site rather than taking the wrapper's name for its scope.

The stubbed call is a pure probe, and the real work goes through paths this test still mocks and asserts on. process.kill appears exactly once in packages/cli/src/commands/review/cleanup.ts, at line 679: process.kill(Number(m[1]), 0). Signal 0 never delivers anything — it only asks whether the pid exists — and the surrounding code treats it purely as an oracle, with alive = (e as NodeJS.ErrnoException).code !== 'ESRCH' at line 689 and the comment above it spelling out that ESRCH is the dead-pid signal. The actual reaping runs through execFileSync (line 733, the tmux -L <name> kill-server invocation) and the unlink through rmSync, both of which this suite mocks and asserts against. So forcing the probe to answer ESRCH changes only the premise "this launcher is dead"; it cannot suppress a kill, an unlink or an error the case is supposed to observe.

The assertion surface is untouched, which is what keeps the case load-bearing. The diff retains not.toThrow() and the writeStdoutLine expectation on Reaped orphaned capture server: ${orphan}, and the mocks.rmSync implementation still throws EBUSY. That combination is the actual subject of the test — a post-kill unlink failure must not abort the sweep — and it still fails if the guard is removed, because the EBUSY throw would then propagate out of runCleanup. Pinning liveness does not weaken any of that; it removes an unrelated way for the same case to go red.

The helper is exception-safe. withDeadPids (lines 1456-1466) saves realKill, installs a function that always throws ESRCH, invokes fn() inside try, and restores in finally. A failing assertion therefore cannot leak the stub into later cases in the file, which is the usual hazard with monkey-patching a process global mid-suite.

This was the last fixture in the block that bet on the host's pid table. The other hardcoded-pid orphan cases already route through the same helper (lines 1479 and 1509) or install an explicit error stub for the case they are about — the EINVAL and EPERM fixtures — and the remaining fixtures derive a pid from a real spawnSync, which is dead by construction. So the change makes the block uniform rather than patching one instance of a pattern that persists elsewhere.

The head moved by two merges from main after the bot's approval, and neither touched this file. The bot's review names ff86f888 while the live head is 918245ea; compare between them reports 27 changed files with status=ahead, behind_by=0, and filtering that list for review/cleanup returns nothing. The test file is byte-identical across the two heads, so that round's reading of the whole file — including its enumeration of the sibling fixtures, which matches mine — applies to what I am approving.

On CI, and this closes the one caveat that round left open. It flagged Test (ubuntu-latest, Node 22.x) and web-shell E2E Smoke (ubuntu-latest, Node 22.x) as cancelled rather than green at the earlier head, hitting the ~30-minute ceiling, and said they needed a re-run before merge. At this head both completed and passed — Test (ubuntu-latest, Node 22.x) in 20m45s, which is the lane that actually executes this suite, and web-shell E2E Smoke in 39m39s. Lint & Static (7m7s), Integration Tests (no-AK, No Sandbox) (17m36s), TUI parity snapshots, OpenTUI no-flicker gate and Check TUI impact all passed as well. There are zero failing and zero pending checks on this head.

There were no prior reviews requesting changes and no inline threads on this PR, so there is no earlier blocking finding to re-verify. What I did not do is re-run the suite myself: the 109-test tally and the before/after reproduction — renaming the fixture to a resident pid to make the old assertion fail with Nothing to clean for target "local" — are the author's measurements. The Test (ubuntu) lane passing on this head is my evidence that the suite is green, and the code reading above is my evidence that it is green for the right reason.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking findings.
Approval blockers: none.

Tier: Scan — test-only change, no production code touched.

Checked:

  • withDeadPids helper at cleanup.test.ts:1456-1465: saves and restores process.kill in a finally block, throws ESRCH to make all pids appear dead to the sweep. Restore is unconditional even if fn() throws.
  • Two sibling orphan cases at lines 1479 and 1509 use the identical wrapper; this case was the only one in the block without it.
  • The try/finally in the patched test resets mocks.rmSync; withDeadPids is called inside that try, so the pid-stub cleanup nests correctly within the mock-reset cleanup.
  • No other test cases in the file use a hardcoded bare pid without a liveness stub or without process.pid as a reliably alive pid.
  • Latest two commits (ff86f8885f2f, 918245eab316) are merges from main; the code change is confined to 9aadf13efcea.

Cross-check: qwen-code-review-bot approved at ff86f8885f2f with no findings. Confirmed: the code change is unchanged between that SHA and current HEAD.

Not covered: no production-code change, no persisted format or protocol touched — deeper rungs not applicable.

Reviewed with AI assistance.

@yiliang114
yiliang114 merged commit fde4c92 into main Oct 28, 2026
64 checks passed
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.26.3.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Main CI failed: Qwen Code CI — src/commands/review/cleanup.test.ts > â€Ķ > keeps the run alive when the post-kill UNLINK fails

4 participants