Skip to content
Latchkey

bash trap not firing on exit or error in CI

A trap handler only runs for the signals it is registered on, and only if it was set before the script exits. A cleanup that never runs in CI usually means the trap targeted the wrong pseudo-signal (ERR vs EXIT), was set too late, or the process was killed by a signal the trap does not catch.

What this error means

Temp files are left behind, or an error-reporting handler never prints, even though the script clearly failed. The trap line exists but the handler did not execute.

bash
# ERR does not fire for a failure inside an if-condition,
# and quoting the wrong way expands $? at trap-set time, not fire time:
trap "echo exit code $?" EXIT   # captures $? now (0), not at exit

Common causes

The handler string was expanded too early

Double quotes expand $?, $LINENO, and variables when the trap is set, not when it fires. Use single quotes so expansion is deferred.

Wrong signal, or trap set after the failure

A trap ... ERR does not fire in contexts where errexit is suppressed (if/while conditions, &&/||), and a trap registered after a command cannot catch that command's exit.

How to fix it

Single-quote the handler and set it early

Register the trap at the top of the script and single-quote the command so $? is read when the trap fires.

bash
cleanup() { rm -f "$tmp"; }
trap cleanup EXIT
trap 'echo "failed at line $LINENO (exit $?)"' ERR

Use EXIT for guaranteed cleanup

EXIT runs on normal and most abnormal exits, so it is the reliable place for cleanup. Add INT TERM if you must catch interrupts.

bash
trap cleanup EXIT INT TERM

How to prevent it

  • Single-quote trap commands so variables expand at fire time.
  • Register traps before the code they must protect.
  • Prefer EXIT for cleanup; ERR is skipped in conditions.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →