Node UnhandledPromiseRejection Crashes the Job in CI - Handle the Rejection
By Kaveh Alemi·Latchkey
On modern Node the default unhandled rejection mode is throw, so a promise that rejects with no catch terminates the process and fails the CI job.
What this error means
A node process prints UnhandledPromiseRejection (or an unhandled rejection stack) and exits non-zero. The rejecting promise was never awaited or caught.
node
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block, or by rejecting a
promise which was not handled with .catch().
Diagnose it: the shell in CI is not your shell
Package scripts run under a different shell, a different PATH, and a non-interactive environment on a runner. Most scripts that fail only in CI are relying on something the login shell gave them locally: a tool on PATH, an environment variable from a dotfile, or a TTY.
Terminal
# what the script can actually see
npm run env | grep -E "^(PATH|NODE_ENV|CI)="
# is the binary on PATH for the script, not just for you?
npm exec -- which <tool> || echo "not resolvable from npm scripts"
# run the exact script with tracing
sh -x -c "$(node -p "require('./package.json').scripts.build")"
Common causes
A promise without await or catch
An async call is fired and forgotten; when it rejects there is no handler, so the default throw mode kills the process.
A missing await inside an async function
A rejection bubbles out of an un-awaited inner promise instead of being caught by the surrounding try/catch.
How to fix it
Await and wrap in try/catch
Await the promise so its rejection is observable.
Wrap the await in try/catch to handle the failure.
A multi-command script can report success while a middle command failed, which produces the worst kind of CI result: a green build that shipped something broken.
.github/workflows/ci.yml
# pipefail is NOT set by default in every runner shell- name:Buildshell:bashrun:|set -euo pipefailnpm run build | tee build.log
How to prevent it
Enable the no-floating-promises lint rule, await every promise or attach a catch, and keep the default throw rejection mode so unhandled rejections fail loudly in CI.
Frequently asked questions
What causes Node UnhandledPromiseRejection crashes the job in CI?
There are 2 common causes: a promise without await or catch and a missing await inside an async function. An async call is fired and forgotten; when it rejects there is no handler, so the default throw mode kills the process.
How do I fix Node UnhandledPromiseRejection crashes the job in CI?
There are 2 fixes depending on which cause you have: await and wrap in try/catch and add a global handler as a backstop. Work through them in order, since the first is the most common.
What does Node UnhandledPromiseRejection crashes the job in CI actually mean?
A node process prints UnhandledPromiseRejection (or an unhandled rejection stack) and exits non-zero.
How do I stop Node UnhandledPromiseRejection crashes the job in CI happening again?
Enable the no-floating-promises lint rule, await every promise or attach a catch, and keep the default throw rejection mode so unhandled rejections fail loudly in CI.