A CI step running a bash script reports exit code 127 on one host, 126 on another, and 137 on a third. What does each of those exit codes tell you?
answer
- some codes come from the shell, not the app
- one means it could not be found
- one means found but not runnable
- anything above 128 smells like a signal
- the value is only eight bits wide
basics
~20 sIn bash, 127 means the command was not found, 126 means it was found but could not be executed, and 137 is 128+9 — the process was terminated by signal 9, typically an out-of-memory or forced kill. All three come from the shell, not from the program's own error codes.
solid answer
~50 sBash reserves a few status values so you can tell shell-level failures from an application's own error codes. `127` means the shell could not find the command at all — a typo, a tool missing from the image, or a PATH that differs from your interactive one; a script whose shebang names a missing interpreter also surfaces as `127`. `126` means the file was found but could not be executed: the executable bit is missing, or you tried to run a directory or a file on a `noexec` mount. Anything of the form `128+N` means the process was killed by signal `N`, so `137` is signal 9 (a forced kill, most often a container memory limit or the OOM killer), `143` is signal 15 (something asked it to stop), and `130` is a Ctrl-C interrupt. Since the visible status is only 8 bits, `exit 300` wraps to `44` and `exit -1` shows as `255`.
code
bash · 5 lines#!/usr/bin/env bash
bash -c 'definitely-not-a-real-command'; echo "not found -> $?"
bash -c '/etc'; echo "not executable -> $?"
bash -c 'kill -TERM $$'; echo "killed by SIGTERM -> $?"
bash -c 'exit 300'; echo "exit 300 wraps to -> $?"go deeper
Memorise the two shell codes: 127 means the command was not found, 126 means it was found but not executable. Being able to name those two already speeds up most debugging.
Explain the 128+N convention and decode the common values — 130, 137, 143 — and know that the visible status is eight bits, so large exit arguments wrap.
Turn the number into an action: 137 in a container sends you to memory limits, 143 to timeouts and shutdown, 127 to PATH and the non-interactive environment. Be ready to say what you would inspect first for each.
Treat exit codes as an operational contract. Decide which small codes your scripts publish, keep them clear of the reserved range, and make sure alerting distinguishes a platform kill from a genuine application failure instead of paging on any non-zero.
## Why some codes are reserved A program can return any status from `0` to `255`, and most pick their own meanings. To keep its own failures distinguishable from an application's, bash reserves a small set of values at the top of the range. Recognising them turns an opaque CI failure into a one-line diagnosis. ## 127 — command not found The shell looked for the command and did not find it. Bash also writes a message such as `foo: command not found` to standard error, but in a CI log that message is often buried while the number is right there in the step summary. Causes, roughly in order of frequency: - A typo in the command name. - The tool is genuinely not installed in that image or on that runner. - `PATH` differs from the one you have interactively, because the script runs in a non-interactive shell that reads different startup files — a classic cron and `ssh host cmd` failure. - The shebang line names an interpreter that does not exist, for example `#!/usr/local/bin/python3` on a host that has it elsewhere. The script file exists and is executable, yet the attempt still fails to find the interpreter and surfaces as `127`. The same happens when a script edited on Windows carries a trailing carriage return, making the interpreter path `/bin/bash\r`, which no host has. The fix is usually to install the tool, use an absolute path, or set `PATH` explicitly at the top of the script. A defensive script can check up front with `command -v tool >/dev/null || { echo "tool is required" >&2; exit 1; }`, which turns an obscure `127` deep in the run into a clear message at the start. ## 126 — found but not executable The shell resolved the name to a file and then could not run it. Typical causes: - The executable bit is not set — very common when a script arrives through a zip archive, an artifact download, or a git checkout on a filesystem that lost the mode. - The path names a directory rather than a file. - The file sits on a mount with the `noexec` option, which is common for `/tmp` on hardened hosts and immediately explains why a downloaded installer works in one location and not another. The distinction from `127` is the whole diagnostic value: `127` says "I could not find it", `126` says "I found it and was not allowed to run it". Checking `ls -l` and the mount options resolves nearly every `126`. ## 128+N — terminated by a signal When a command is killed by a signal rather than exiting on its own, bash reports `128 + N`, where `N` is the signal number. The arithmetic is the shell's reporting convention; what the signals themselves mean to the operating system is a platform topic. The ones that show up in real logs: | status | signal | usual story | |---|---|---| | 130 | 2 (INT) | someone pressed Ctrl-C, or a job was interrupted | | 137 | 9 (KILL) | forced kill — container memory limit or an OOM kill | | 143 | 15 (TERM) | an orchestrator or scheduler asked the job to stop | | 141 | 13 (PIPE) | a reader closed a pipe early | `137` is the one to recognise instantly in container work: a step that dies with `137` and no error output was almost never a bug in your script's logic — it ran out of the memory the platform allowed it. `143` similarly points at a timeout or a shutdown rather than at your code. `kill -l 137` will name the signal for you at a shell. A subtlety worth mentioning: because these are ordinary status numbers, a program is free to `exit 137` on its own, so the mapping is a strong hint rather than proof. Combine it with the log and with the platform's own metrics before concluding. ## The 0-255 range and wrapping The status a parent sees is only eight bits. `exit 300` therefore reports `44` (300 minus 256), and `exit -1` reports `255`. This bites scripts that try to pass through a count — `exit "$error_count"` looks reasonable until 256 errors report perfect success. If you must convey a number, print it and exit with a small fixed code. Stay inside `1`-`125` for your own error codes to avoid colliding with the reserved values, and document what each one means for whoever branches on them. ## What to say in an interview Give the three meanings crisply, say that `128+N` is the shell's way of reporting a signal death, and immediately connect `137` to memory limits — that connection is the reason interviewers ask.
- A CI step exits 137 with no error message in the log. What do you check first?Memory. 137 is 128+9, a forced kill, and in containers that is overwhelmingly the platform enforcing a memory limit rather than your script failing. Check the runner or pod memory limit and any OOM record on the host, then the peak usage of the heaviest child process. Only after ruling that out look for something in the pipeline deliberately sending signal 9.
- Why do 126 and 127 deserve separate codes at all?They separate two different repairs. 127 means resolution failed — install the tool, fix PATH, correct the shebang. 126 means resolution succeeded and execution was refused — set the executable bit, stop pointing at a directory, or move off a noexec mount. Collapsing them into one generic failure would cost you the first and cheapest diagnostic step.
- Which exit codes should your own scripts use for their own errors?Small ones, roughly 1 through 125, so they never collide with 126, 127 or the 128+N signal range. Reserve 1 for the generic failure and give distinct low numbers to conditions a caller might branch on, such as bad usage or a missing dependency. Document them, and never exit with an unbounded count, because the status is truncated to eight bits.
saying these in an interview costs you the question
- Reads 137 as an application error code rather than a kill
- Cannot distinguish command-not-found from not-executable
- Thinks exit codes can be larger than 255
- Assumes any non-zero exit means the script logic was wrong
- Believes 127 means the file is missing rather than unresolvable