"tsx watch" Exits Immediately in CI - Fix Watch-Mode in Pipelines
Watch-mode runners like tsx watch are built to stay running and re-run on file changes. In CI that is the wrong shape - the step either hangs forever waiting for changes or exits immediately when stdin closes, neither of which is a real run.
What this error means
A CI step invoking tsx watch src/index.ts either never returns (the job times out) or returns instantly with the process gone, because the watcher had no TTY and exited. The same command works on a developer machine.
$ tsx watch src/index.ts
# CI: hangs until the job timeout kills it
# or exits at once because stdin/TTY closedCommon causes
Watch mode used where a one-shot run is needed
watch keeps the process alive to react to edits. CI wants the script to run once and exit with a status - the two are fundamentally different lifecycles.
No TTY/stdin on the runner
Some watchers tie their lifetime to stdin or a TTY. On a headless runner those are absent, so the watcher exits at once or behaves erratically.
How to fix it
Run once, not in watch mode
Use the non-watch invocation in CI and reserve watch for the local dev script.
// package.json
{
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts"
}
}
// CI: npm run start (not the dev/watch script)Separate dev and CI scripts
- Keep
watchonly in thedevscript developers run interactively. - Have CI call the plain runner (
tsx,node, or the built output). - Never put a watcher on the critical path of a pipeline step.
How to prevent it
- Reserve
--watch/watchfor local development scripts. - Run a single-shot command in CI and let it exit with a status.
- Add a job-level timeout so an accidental watcher cannot hang forever.