Skip to content

Native tsc ignores SIGINT and SIGTERM while compiling β€” Ctrl-C does not interrupt a build, and the process exits 0Β #64196

Description

πŸ”Ž Search Terms

SIGINT, SIGTERM, ctrl-c, ctrl+c, signal, interrupt, tsgo does not exit,
native tsc kill, exit code 0 killed, cancel build

πŸ•— Version & Regression Information

  • This changed between versions 6.x (the JavaScript compiler) and 7.0 (the native compiler).
    Running the TS 6 tsc.js under Node and sending SIGTERM mid-compile terminates it
    immediately; the native tsc in 7.0.2 ignores it and runs to completion.
  • Also reproduces on typescript@7.1.0-dev.20260908.1 (nightly), so it is not already fixed.
  • Tested on macOS 15 (Darwin 25.5.0), arm64, @typescript/typescript-darwin-arm64.

⏯ Playground Link

No response

πŸ’» Code

Not reproducible in the Playground β€” this is CLI/process behaviour. Any project large enough
to leave a signalling window will do. This generator produces one using nothing but plain
interfaces and functions (no errors, no deep generics, no external libraries):

// gen.js
const fs = require('fs');
fs.mkdirSync('src', { recursive: true });
for (let f = 0; f < 30000; f++) {
  let s = '';
  for (let i = 0; i < 20; i++) {
    s += `export interface I${f}_${i} { id: string; n: number; ok: boolean; tags: string[]; at: Date; }\n`;
    s += `export function f${f}_${i}(x: I${f}_${i}): I${f}_${i} { return { ...x, n: x.n + 1 }; }\n`;
    s += `export const c${f}_${i} = f${f}_${i}({ id: "a", n: ${i}, ok: true, tags: ["t"], at: new Date() });\n`;
  }
  if (f > 0) s += `import { f${f - 1}_0 } from './m${f - 1}';\n`;
  fs.writeFileSync(`src/m${f}.ts`, s);
}
fs.writeFileSync('tsconfig.json', JSON.stringify({
  compilerOptions: { strict: true, target: 'ES2020', module: 'commonjs', noEmit: true, skipLibCheck: true },
  include: ['src'],
}, null, 2));
node gen.js
npm i typescript@7

TSC=node_modules/@typescript/typescript-<platform>/lib/tsc

$TSC -p tsconfig.json --extendedDiagnostics > out.txt 2>&1 &
PID=$!
sleep 10
ps -p $PID              # confirm it is genuinely still compiling
kill -INT $PID          # or -TERM
wait $PID; echo "exit=$?"
grep 'Total time' out.txt   # if this prints, the compile finished anyway

Confirming the process is still alive before signalling matters β€” a build that finishes
first gives a false negative.


πŸ™ Actual behavior

SIGINT and SIGTERM sent to a compile in progress are ignored completely. The compiler
finishes the whole program, prints its full diagnostics, and exits 0, as though no signal
had been sent. Ctrl-C therefore cannot interrupt a build.

Signal sent 10 s into a compile of 30 000 files / 1.9 M lines / 0 errors:

Signal Exit code Outcome
SIGINT 0 Ignored. Full diagnostics printed β€” compile ran to completion.
SIGTERM 0 Ignored. Full diagnostics printed β€” compile ran to completion.
SIGHUP 129 Terminates ~0.3 s after the signal. No diagnostics.
SIGINT, idle --watch 0 Exits within ~1 s, as expected.

"Ran to completion" is not inferred from wall-clock timing β€” the interrupted runs emit the
full Files / Lines / Check time / Total time diagnostics block, which only a finished
compile prints.

Two controls show the signals are genuinely reaching the process and that a handler exists:
SIGHUP kills a running compile instantly, and SIGINT works normally in --watch mode
while idle. SIGINT/SIGTERM appear to be trapped and then not acted upon while compiling.

πŸ™‚ Expected behavior

SIGINT and SIGTERM should stop the compile promptly and exit non-zero β€” conventionally
130 and 143. This is what the TypeScript 6 JavaScript compiler does.

Additional information about the issue

Two practical consequences beyond Ctrl-C being unresponsive:

  1. A cancelled build reports success. Exiting 0 after SIGTERM means any supervisor that
    terminates tsc β€” a CI job timeout, a cancelled workflow, a build runner stopping a task β€”
    records the build as having passed.

  2. Build orchestrators leak compiler processes. A tool that builds several packages in
    parallel and stops at the first failure signals the remaining ones with SIGTERM and exits.
    Because they ignore it, they are reparented to init/launchd and keep running β€”
    uninterruptible and invisible β€” each holding gigabytes of heap. In our monorepo a single
    failing service left 16 compilers running and exhausted system memory. pnpm 12 works around
    it by escalating to SIGKILL; pnpm 11 does not, and neither does Ctrl-C.

Activity

  1. RyanCavanaugh commented on Sep 8, 2026

    @RyanCavanaugh
    Member

    What's the difference between this and #63856?

  2. rafaelnajman commented on Sep 8, 2026

    @rafaelnajman
    Author

    None at root β€” same bug, and #63856 already identifies the cause (signal.NotifyContext in cmd/tsgo/main.go cancelling a context the build never observes). I searched the archived typescript-go repo and on "SIGINT"/"SIGTERM", so a title reading "ctrl-c" never surfaced for me. Closing this as a duplicate.

    The two things here that weren't on #63856 β€” the interrupted build exiting 0, and parallel build runners leaking compilers β€” I've added there instead: #63856 (comment)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions