Skip to content

fix: replace async-exit-hook with exit-hook to preserve process.exitCode - #31

Open
AmineMaila wants to merge 1 commit into
leinelissen:mainfrom
AmineMaila:fix-exit
Open

fix: replace async-exit-hook with exit-hook to preserve process.exitCode#31
AmineMaila wants to merge 1 commit into
leinelissen:mainfrom
AmineMaila:fix-exit

Conversation

@AmineMaila

Copy link
Copy Markdown

Description

Replaces async-exit-hook with exit-hook to fix a bug where process.exitCode was always overridden to 0 on process exit.

async-exit-hook unconditionally calls process.exit(0) after its cleanup hooks run, ignoring any process.exitCode set by the application. The exit-hook package (by the same author, sindresorhus) hooks into process exit events without forcing the exit code, preserving whatever process.exitCode was set.

I encountered this while using vitest and embedded-postgres together for testing — when a test failed, vitest sets process.exitCode = 1, but async-exit-hook would force it back to 0, causing CI pipelines to incorrectly report the test run as successful.

Changes

  • packages/embedded-postgres/package.json: Replaced async-exit-hook dependency with exit-hook
  • packages/embedded-postgres/src/index.ts: Updated import from AsyncExitHook to asyncExitHook, removed the done callback parameter from gracefulShutdown, added a 5-second wait timeout
  • packages/embedded-postgres/tests/index.test.ts: Added test verifying that process.exitCode is preserved when using EmbeddedPostgres
  • packages/embedded-postgres/tests/helpers/exit-code-test.mjs: Helper script for the exit code test

Testing

  • All 8 existing + new tests pass
  • Verified that a child process using EmbeddedPostgres with process.exitCode = 42 exits with code 42 (not 0)

mgray88 added a commit to first-quality-books/embedded-postgres that referenced this pull request Aug 25, 2026
…ster

async-exit-hook calls `process.exit(0)` unconditionally once its hooks have
run, discarding whatever `process.exitCode` the application set: a vitest run
that fails while a cluster is up reports success. It has also been unmaintained
since 2019 and ships no types.

exit-hook preserves the exit code and needs no completion callback, which
retires the `done` guard from the previous commit. Taken on its own, though, it
trades one line of exit noise for another. It prints a SYNCHRONOUS TERMINATION
NOTICE on every explicit `process.exit()` for as long as an asynchronous hook
is registered, and registering one at import time puts that warning on the exit
of every process that loads this module, running cluster or not.

So the hook is attached per running cluster instead: `start()` attaches it,
`stop()` detaches the last one, and a postmaster that dies on the way up is
untracked rather than left for `gracefulShutdown` to wait out its timeout on.
An embedder that stops its clusters before exiting now exits silently, and the
warning means what it says when a postmaster really is about to be orphaned.
`instances` tracks running clusters rather than constructed ones for the same
reason.

engines.node moves to >=20, which is what exit-hook@5 requires. The new tests
run the scenarios in a child process, because every guarantee here is about a
process on its way out; `beforeEach` also moves to vitest from `node:test`,
where it was registering with a runner that never ran it.

Refs leinelissen#31
@adamsondavid

Copy link
Copy Markdown

Hi, is there any recent update on this PR?

@leinelissen

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.

2 participants