fix(test): make tool/shell.test.ts deterministic (fast commands lost output) - #11913
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Both previously reported issues remain fixed at the current commit:
No new issues found. Files Reviewed (4 files)
Previous Review Summaries (2 snapshots, latest commit 71a0ad7)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 71a0ad7)Status: No Issues Found | Recommendation: Merge Both previously reported issues are fixed in this update:
No new issues found in the incremental diff. Files Reviewed (4 files)
Previous review (commit ffb0093)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (4 files)
The root-cause analysis and fix approach (synchronous stdio tap at spawn time + awaiting the reader fiber before scope teardown) are sound, and the diff follows fork-merge hygiene well (Kilo-specific logic isolated in Reviewed by claude-sonnet-5-20260630 · Input: 34 · Output: 11.4K · Cached: 802.5K Review guidance: REVIEW.md from base branch |
71a0ad7 to
3203f79
Compare
…output) tool/shell.test.ts intermittently failed in CI (e.g. falls back from terminal-only configured shell [159.79ms]) with result.output being "(no output)". Reproduced locally at ~13% across two test cases, so this is a real timing race rather than a one-off flake. Bun's child_process discards buffered stdout/stderr once the child emits "close", and our CrossSpawnSpawner attaches stream readers lazily, so fast-exiting processes lose all output before the reader attaches. The same path serves the live shell tool, so the test is correctly catching a product bug. Tap stdout/stderr into PassThroughs synchronously at spawn time, and await the reader fiber after the process exits so scope teardown cannot interrupt it before trailing chunks are drained.
3203f79 to
70a002d
Compare
fix(cli): retain shell output for fast-exiting commands
This fixes a non-deterministic test
test/tool/shell.test.tsintermittently fails in CI, e.g. the original run reported:Locally it reproduced at ~13% (2/15 runs failing) across two test cases (
falls back from terminal-only configured shellandcaptures stderr in output), so it is a genuine timing race, not a one-off flake. The failure is non-deterministic because it depends on whether the reader fiber attaches before the process exits, which varies with scheduler load.Root cause
Bun's
child_processsilently discards buffered stdout/stderr once the child emitsclose. OurCrossSpawnSpawnerattaches stream readers lazily (the Effect stream consumer runs a tick or more after spawn), so any process that exits before the reader attaches loses all of its output. A standalone repro confirms a reader attached even 5ms after spawn gets zero bytes fromecho.The same path serves the live shell tool, so fast commands can return
(no output)in the real product too — the test is correctly catching that, which is why it is worth fixing rather than deleting.Fix
packages/core/src/kilocode/stdio-tap.ts(new) —tap(proc)pipesproc.stdout/stderrintoPassThroughstreams synchronously at spawn time, before the event loop yields. The taps buffer the data (bounded by PassThrough highWaterMark) so it survives until the lazy reader attaches. Storage uses aWeakMapso taps are GC'd with the process.packages/core/src/cross-spawn-spawner.ts— calltap(proc)in the same tick aslaunch(...), and swapsetupOutput'sevaluatecallbacks to read the tapped streams viatapped(proc, "stdout"/"stderr")(falling back to the raw stream). Four single-linekilocode_changemarkers; no signature or structural changes, to keep upstream merges trivial.packages/opencode/src/tool/shell.ts— the spawner fix retains the data, but scope teardown after exit can still interrupt the reader fiber before it drains the final chunks. Keep the reader fiber andFiber.awaitit (3s timeout, errors ignored) after the exit/abort/timeout race resolves, so trailing output is not dropped.Verification
test/tool/shell.test.ts: 30/30 green (was ~13% failure locally: 2/15 runs failing before the fix).test/tool/: 310 pass / 0 fail.check-opencode-annotations.tsclean.This affects upstream OpenCode equally (they also run on Bun), so it is a candidate to upstream, which would let us drop the markers.