Skip to content
Latchkey

npm pre/post Script Failing the Run - Fix Lifecycle Hook Errors in CI

npm automatically runs pre<name> before and post<name> after npm run <name>. If the pre/post hook exits non-zero, the whole run fails - sometimes confusingly, since the main command itself may be fine.

What this error means

npm run build (or test/start) fails, but the error is from a prebuild/postbuild hook, not the build itself. The main script may never even run because the pre hook failed first.

npm output
> app@1.0.0 prebuild
> node scripts/check-env.js

Error: missing required env var API_URL
npm error code 1
# "build" never ran because "prebuild" failed

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 pre hook failed before the main script

npm runs pre<name> first; if it exits non-zero (a failing check, a missing tool), the main script never runs and the whole command fails.

A post hook failed after a successful main script

A post<name> step (cleanup, notification, upload) that fails marks the run as failed even though the main command succeeded.

How to fix it

Identify which lifecycle step failed

Read the script-name banner npm prints to see if the failure is pre, main, or post.

Terminal
# run the main step alone to isolate it
npm run build --ignore-scripts   # skips pre/post around explicit runs
# or run the hook directly to debug it
npm run prebuild

Keep lifecycle hooks intentional

  1. Reserve pre/post hooks for steps that genuinely must gate the main command.
  2. Move optional/cleanup work into separate explicit scripts so it cannot fail the main run.
  3. Make hook failures actionable (clear error messages) so CI logs point at the cause.

Make failures fail the job

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: Build
  shell: bash
  run: |
    set -euo pipefail
    npm run build | tee build.log

How to prevent it

  • Use pre/post hooks only for genuinely gating steps.
  • Move optional work out of the lifecycle chain.
  • Make hook errors clear so CI failures are easy to trace.

Frequently asked questions

What causes npm pre/post script failing the run?
There are 2 common causes: a pre hook failed before the main script and a post hook failed after a successful main script. npm runs pre<name> first; if it exits non-zero (a failing check, a missing tool), the main script never runs and the whole command fails.
How do I fix npm pre/post script failing the run?
There are 2 fixes depending on which cause you have: identify which lifecycle step failed and keep lifecycle hooks intentional. Work through them in order, since the first is the most common.
What does npm pre/post script failing the run actually mean?
npm run build (or test/start) fails, but the error is from a prebuild/postbuild hook, not the build itself.
How do I stop npm pre/post script failing the run happening again?
Use pre/post hooks only for genuinely gating steps. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card