Skip to content

fix(ts): keep SIGINT fatal for the core's run in the npm launcher - #1167

Merged
thekevinscott merged 3 commits into
mainfrom
claude/1166-ts-sigint
Sep 24, 2026
Merged

thekevinscott merged 3 commits into
mainfrom
claude/1166-ts-sigint

Conversation

@thekevinbot

@thekevinbot thekevinbot commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1166

Ctrl-C did not interrupt a run in progress through the npm launcher. The
launcher's process.on("SIGINT") listener cannot fire during a scan: the
addon's runCli export is synchronous and blocks the event loop for the whole
of the core's run, so the listener is only reached once the work has already
finished.

The addon now sets SIGINT and SIGTERM to SIG_DFL for the duration of the
core's run and restores the prior disposition after — the Node analogue of the
Python launcher's fix in #1165, one layer lower because JS cannot reach a
signal disposition.

Measured

Probed against the built addon with the launcher's two listeners registered,
over a config whose on-file hook blocks (sleep 120). The spawner resets
SIGINT to SIG_DFL before exec — a non-interactive shell sets SIG_IGN for
& jobs, which hides the difference.

SIGINT disposition during runCli query mid-scan dirsql server shutdown
JS listeners only (before) survives 2x SIGINT, SIGKILL-only 0
no JS listeners at all dies, 130 survives, SIGKILL-only
SIG_DFL below JS, listeners kept (this PR) dies, 130 0 (3/3 runs)

The middle row is why keepSignalsFatal stays: registering the listeners is
what gives signal-hook a disposition to chain to, and without it server
shutdown never runs. SIGTERM mid-scan dies at 143 with this change.

Hand-run against the built dist/cli/dirsql.js: query mid-scan dies of SIGINT,
server exits 0.

Red/green

The behavior lives at the e2e tier (a real launcher process and a real signal),
and no CI lane observes it: e2e is local-only by policy, and the
rust-napi-binding lane runs colocated-test + unit-lint only. CI was green
on the pushed test-only commit (115c459) for that reason, so the AGENTS.md
"CI red before implementation" gate could not be met here. Local evidence
instead:

  • 115c459 (test only) — vitest run --dir tests/e2e sigint-interrupt:
    AssertionError: expected 'still running' to deeply equal [ null, 'SIGINT' ]
  • 618f340 (implementation) — same command passes; full TS e2e suite 18/18.

A colocated napi unit test for the new guard was written and then removed: the
unit-lint gate's no-out-of-module-call rule rejects a unit test that calls
libc::signal, and injecting a trait double around two FFI calls is not worth
the seam.

Changelog / Migrations

  • Changelog fragment added under packages/ts/changelog.d/
  • Migration fragment added under packages/ts/migrations.d/ (runtime
    behavior change: exit status of an interrupted run)

E2E Verification

  • Ran e2e suites locally for every affected SDK
  • Python SDK e2e: N/A (no python source changed)
  • TypeScript SDK e2e: pass
  • Rust core e2e (if applicable): N/A (no core change)
  • packages/python/e2e-attestations/<branch>.json receipt written if packages/python changed: N/A
  • packages/ts/e2e-attestations/<branch>.json receipt written (just e2e-attest-ts)
  • Command(s) run: pnpm test:e2e (packages/ts), pnpm test (packages/ts),
    cargo test --manifest-path packages/ts/napi/Cargo.toml, just preflight
  • Result summary: e2e 18/18 pass, unit+integration+binding 248/248 pass,
    preflight 0 failing pairs.

A new CLI e2e case blocks the scan in an `on-file` hook and asserts the
launcher dies of the signal.

skip-changelog: tests only
The launcher's JS listeners cannot fire while `runCli` runs: the napi export
is synchronous and blocks the event loop for the whole of the core's run, so
a signal arriving mid-scan was absorbed and the process was SIGKILL-only.
The addon now sets SIGINT and SIGTERM to SIG_DFL for that window and restores
the prior disposition after. `dirsql server` keeps its exit code: the core
registers its own handler over SIG_DFL once it starts.
@thekevinscott
thekevinscott merged commit 9faa200 into main Sep 24, 2026
77 checks passed
@thekevinscott
thekevinscott deleted the claude/1166-ts-sigint branch September 24, 2026 01:48
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.

Ctrl-C does not interrupt a running query in the npm launcher

2 participants