CI Exit Codes: What Each One Actually Tells You
Exit code 137 is not a build error. It is the kernel killing your process for memory, and nothing in the application log will explain it.
CI logs surface exit codes without interpretation, and several of the common ones are not application errors at all. Codes above 128 mean the process was terminated by a signal, which points at the environment rather than the code.
The other trap is not an exit code at all: a pipeline reports the status of its last command unless pipefail is set, so a failing build piped to tee exits zero.
Codes you will actually see
| Code | Meaning | What to do |
|---|---|---|
0 | Success | |
1 | General error | Read the log; it is a real failure |
2 | Shell misuse, or tool-specific | Frequently a bad flag or missing file |
126 | Command found but not executable | Check the executable bit and shebang |
127 | Command not found | Not on PATH in the runner shell |
128 | Invalid argument to exit | |
130 | SIGINT (128 + 2) | Interrupted, usually cancellation |
137 | SIGKILL (128 + 9) | Almost always the OOM killer |
139 | SIGSEGV (128 + 11) | Segfault in a native component |
143 | SIGTERM (128 + 15) | Graceful termination, often a timeout |
Exit 137 is a memory problem
# confirm it was the OOM killer
docker inspect <container> --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
# see peak usage before it dies
/usr/bin/time -v <command> 2>&1 | grep "Maximum resident set size"
# common remedies
node --max-old-space-size=4096 ... # raise the JS heap
cargo build -j 2 # fewer parallel jobs
# or move the job to a larger runnerThe zero that should not be zero
# WITHOUT pipefail, this exits 0 even when the build fails,
# because tee succeeded
- run: npm run build | tee build.log
# WITH pipefail, the pipeline fails when any element fails
- shell: bash
run: |
set -euo pipefail
npm run build | tee build.logTool-specific non-zero codes that are not failures
pytestexits5when zero tests were collected, which is a configuration problem, not a pass.grepexits1when it matches nothing, which is normal and will fail aset -escript.diffexits1when files differ, which is frequently the expected outcome.terraform plan -detailed-exitcodeexits2when changes are present, which is success.- Always check a tool documented exit codes before treating non-zero as an error.
Reading exit codes in CI
- A shell reports
128 + Nwhen a process is terminated by signal N: 137 is SIGKILL (usually the out-of-memory killer), 143 is SIGTERM, 130 is SIGINT. - Exit code 137 in a container almost always means the kernel killed it for memory. Nothing in the application log will explain it.
- A pipeline reports the exit status of its LAST command unless
pipefailis set, which is how a failing command piped toteeproduces a green build. - Some tools use non-zero codes for non-failure outcomes. Check the tool documentation before treating any non-zero code as an error.
Frequently asked questions
What does exit code 137 mean in CI?
What is exit code 143?
Why did my build pass when a command failed?
pipefail, a shell pipeline exits with the status of its last command. failing-command | tee log exits zero because tee succeeded. Add set -euo pipefail to any multi-line run block.Is a non-zero exit code always a failure?
grep exits 1 on no match, diff exits 1 on differences, pytest exits 5 when no tests were collected, and terraform plan -detailed-exitcode exits 2 when changes exist. Check the tool documentation before treating non-zero as an error.