Skip to content

feat(cli): graceful termination signals + broken-pipe guard#185

Merged
zeshi-du merged 2 commits into
TestSprite:mainfrom
Andy00L:feat/interrupt
Jul 6, 2026
Merged

feat(cli): graceful termination signals + broken-pipe guard#185
zeshi-du merged 2 commits into
TestSprite:mainfrom
Andy00L:feat/interrupt

Conversation

@Andy00L

@Andy00L Andy00L commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Adds process-lifecycle hardening (fixes #75, the SIGINT ask, plus its natural siblings).

Termination signals: without a handler, Node kills the process abruptly on Ctrl+C during a long test run --wait, leaving the user unsure whether the run was cancelled or is still executing server-side (it is: the CLI only polls; the run lives on the backend). Handlers for SIGINT/SIGTERM/SIGHUP now print a one-line explanation plus how to resume (testsprite test list / testsprite test wait <runId>) and exit with the conventional 128+signum code (SIGINT -> 130, SIGTERM -> 143, SIGHUP -> 129).

Broken pipe: piping to a reader that closes early (testsprite ... | head) raised an unhandled write EPIPE and dumped a raw stack (exit 1). The guard swallows EPIPE and exits 0 (the SIGPIPE-equivalent), while any other stream error still surfaces.

  • New: src/lib/interrupt.ts (installSignalHandlers + installBrokenPipeGuard)
  • Wired once in index.ts; process and streams injected, so 7 unit tests cover it without a subprocess or a real signal
  • No new command, so the help surface is unchanged

SIGTERM/SIGHUP reuse the same mechanism as the SIGINT ask; the EPIPE guard is bundled as related process-teardown hardening. Happy to split if you prefer #75 scoped to SIGINT only.

Fixes #75

Summary by CodeRabbit

  • New Features
    • Hardened CLI startup with dedicated handling for SIGINT/SIGTERM/SIGHUP and improved interrupt messaging.
  • Bug Fixes
    • Prevents noisy/stacked broken-pipe (EPIPE) failures when stdout/stderr is piped to commands that exit early.
    • Exits with conventional status codes for termination signals and avoids crashes when output streams are closed.
  • Tests
    • Added a test suite covering interrupt formatting, signal handler behavior, and broken-pipe scenarios.

Install handlers for SIGINT/SIGTERM/SIGHUP that print a one-line explanation
(any started run keeps executing server-side; resume with `testsprite test list`
or `testsprite test wait <runId>`) and exit with the conventional 128+signum
code (SIGINT -> 130). Also guard EPIPE on stdout/stderr so piping to a reader
that closes early (`... | head`) exits cleanly instead of dumping a raw
`write EPIPE` stack. process and streams are injectable, so both are unit-tested
without spawning a subprocess or sending a real signal.

Fixes TestSprite#75
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2411ddd6-0079-4ec0-9add-09f87b54d83c

📥 Commits

Reviewing files that changed from the base of the PR and between 73b6149 and d5341d6.

📒 Files selected for processing (2)
  • src/lib/interrupt.test.ts
  • src/lib/interrupt.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/interrupt.test.ts
  • src/lib/interrupt.ts

Walkthrough

Adds a new src/lib/interrupt.ts module for termination-signal messaging and broken-pipe handling, wires it into src/index.ts startup, and adds Vitest coverage for message formatting, signal exits, and EPIPE behavior.

Changes

Graceful Termination and Broken-Pipe Handling

Layer / File(s) Summary
Interrupt utilities: constants, message formatting, handlers
src/lib/interrupt.ts
Defines TERMINATION_EXIT_CODES, TerminationSignal, SIGINT_EXIT_CODE, formatInterruptMessage(), InterruptDeps, installSignalHandlers(), BrokenPipeDeps, and installBrokenPipeGuard(), handling SIGINT/SIGTERM/SIGHUP and EPIPE errors on stdout/stderr.
Startup wiring in index.ts
src/index.ts
Imports and invokes installSignalHandlers() and installBrokenPipeGuard() during CLI startup.
Interrupt module test suite
src/lib/interrupt.test.ts
Adds Vitest tests for message formatting, signal registration and exit codes, synchronous stderr hint writing, and broken-pipe EPIPE handling.

Estimated code review effort: 2 (Simple) | ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main CLI lifecycle change.
Linked Issues check ✅ Passed The PR implements the requested SIGINT handler, exit code 130, interrupt messaging, wiring, and unit tests.
Out of Scope Changes check ✅ Passed All changes stay within CLI interruption and process-lifecycle handling; no unrelated areas were modified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/interrupt.ts`:
- Around line 57-75: installSignalHandlers() exits immediately after writing the
interrupt hint, so the stderr output can be lost when stderr is piped or
redirected. Update the signal handler in installSignalHandlers to wait for the
stderr writes to complete or flush synchronously before calling exit, and keep
the logic centered around the existing stderr and exit deps plus
formatInterruptMessage/TERMINATION_EXIT_CODES so the hint is reliably emitted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ba898e4-8a5d-48fd-8b21-d31ecca050d8

📥 Commits

Reviewing files that changed from the base of the PR and between b9e9601 and 73b6149.

📒 Files selected for processing (3)
  • src/index.ts
  • src/lib/interrupt.test.ts
  • src/lib/interrupt.ts

Comment thread src/lib/interrupt.ts
A signal handler calls process.exit() immediately after writing the interrupt
hint. When stderr is a pipe, an async process.stderr.write() may not flush
before the process terminates, so the hint could be lost. The default stderr
writer now uses fs.writeSync (best-effort, guarded against EPIPE) so the hint is
reliably emitted. Added a test mocking fs.writeSync to assert the synchronous
write on the default path.
@Andy00L

Andy00L commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review: the signal handler now writes the interrupt hint synchronously via fs.writeSync before exit, so it survives a piped or redirected stderr (an async process.stderr.write() can be dropped when process.exit() fires immediately after). The write is guarded so an already-closed stderr (EPIPE) still exits cleanly. Added a test that mocks fs.writeSync to assert the synchronous default path.

@zeshi-du zeshi-du left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed: scoped, tests included, CI green across the matrix.

@zeshi-du zeshi-du merged commit 946f5b3 into TestSprite:main Jul 6, 2026
9 checks passed
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.

[Hackathon] Graceful Ctrl+C (SIGINT) handling

2 participants