Found by /review-loop on #9 (cross-vendor lens). Deferred from that PR deliberately: the fix is a
termination design, not a one-line guard, and it would have changed the character of that diff.
What happens
indexwright-record -- npm test runs the suite as a child with stdio: 'inherit', so Ctrl-C sends
SIGINT to the whole foreground process group — parent included. packages/record/src/cli.ts
installs no handler for it, so Node's default terminates the recorder immediately. The finally
that closes the capture never runs, and neither does the writeCorpus call after it.
Everything the proxy observed up to that moment is discarded. For a long suite that is the entire
point of the run.
Why it matters more than a normal interrupt
The corpus is written whatever the suite's verdict is — that is a deliberate property, because a
red suite still describes the queries the application issues. Interruption is the one exit path
that silently violates it, and it is also the path a developer takes most often on a slow suite.
Shape of a fix
Not a one-liner, which is why this is its own issue:
- Handle
SIGINT and SIGTERM on the parent.
- Forward the signal to the child rather than killing it out from under the suite, and wait for it
to exit so its own cleanup runs.
- Close the capture, write the corpus, then exit
128 + signal (130 for SIGINT), so the shell
convention runChild already follows is preserved.
- Guard against a second Ctrl-C arriving while the first is being handled — the second should be
allowed to kill immediately rather than hang.
Worth a test that sends SIGINT to a running recorder and asserts a corpus was written and the exit
code is 130.
Found by
/review-loopon #9 (cross-vendor lens). Deferred from that PR deliberately: the fix is atermination design, not a one-line guard, and it would have changed the character of that diff.
What happens
indexwright-record -- npm testruns the suite as a child withstdio: 'inherit', so Ctrl-C sendsSIGINTto the whole foreground process group — parent included.packages/record/src/cli.tsinstalls no handler for it, so Node's default terminates the recorder immediately. The
finallythat closes the capture never runs, and neither does the
writeCorpuscall after it.Everything the proxy observed up to that moment is discarded. For a long suite that is the entire
point of the run.
Why it matters more than a normal interrupt
The corpus is written whatever the suite's verdict is — that is a deliberate property, because a
red suite still describes the queries the application issues. Interruption is the one exit path
that silently violates it, and it is also the path a developer takes most often on a slow suite.
Shape of a fix
Not a one-liner, which is why this is its own issue:
SIGINTandSIGTERMon the parent.to exit so its own cleanup runs.
128 + signal(130 for SIGINT), so the shellconvention
runChildalready follows is preserved.allowed to kill immediately rather than hang.
Worth a test that sends SIGINT to a running recorder and asserts a corpus was written and the exit
code is 130.