VERIFYFIRST // exit-code A command's return status — The command exited zero, so you moved on. WHAT IT CAPTURES Whether the process believed it completed the operation it chose to attempt. WHAT IT CANNOT SEE - Semantics. Zero means 'no error', never 'the thing you wanted is now true'. - No-ops. A command whose behaviour depends on current state can succeed by doing nothing. - Partial completion, where early steps left convincing artifacts before a later step aborted. - Which target was acted on, when the argument resolved differently than intended. - Commands that never return at all, which produce no exit code to inspect. KNOWN FAILURES (9) NS-005 enable --now does not restart an already-running unit reads as The command exits zero and the service is active. Conclusion drawn: the new code is live. actually systemctl enable --now starts a stopped unit. On a running one it is a no-op. The old process, with the old ExecStart, survives. check Compare the unit's ExecStart on disk against the live process: systemctl show -p ExecStart NAME and ps -p $MAINPID -o args= NS-008 set -e aborts a script at a validation step that concerns something else reads as The install script ran and the config file is in place. Conclusion drawn: the change is active. actually A validation step covering the whole configuration failed on an unrelated block that needed an environment variable the script did not load. Under set -e the script exited before the reload. check Ask the running service what it loaded, not the filesystem what it holds. For Caddy: the admin API's live config. NS-013 A teardown script destroys the environment it is executing inside reads as Several long-running sessions vanish at once with no error output. Conclusion drawn: the tool crashed, or the machine failed. actually A rebuild script ran `kill-session` against the multiplexer session it was itself running in. It killed its own parent, taking four unrelated sessions with it. There is no crash and no error because the script did exactly what it was told. check Before any teardown, compare the target against the environment you occupy: for tmux, test whether $TMUX is set and whether its session name equals the target. Refuse if they match. NS-014 A privilege prompt with nowhere to appear hangs instead of failing reads as A deploy step produces no output and does not return. Conclusion drawn: the operation is slow, or the network is stalling. actually The command needed a password. There is no terminal to prompt on, so it waits indefinitely. No error, no exit code, no timeout. check Ask whether credentials are needed before running the real command: sudo -n true returns non-zero immediately when a password would be required. NS-015 A pipeline returns the status of its last command, not its failing one reads as `npm test | tee build.log` exits zero and the log file is written. Conclusion drawn: the tests passed. actually A shell reports the exit status of the last command in a pipeline. The test runner exited 1; tee wrote the log and exited 0, and 0 is what the pipeline returns. `set -e` does not intervene, because the pipeline as a whole succeeded. check Read the whole vector rather than the summary: `false | true; echo "${PIPESTATUS[@]}"` prints `1 0` where `$?` prints `0`. Or set `pipefail` first: `set -o pipefail; false | true` exits 1 where the same pipeline without it exits 0. NS-016 curl exits zero after successfully downloading an error page reads as `curl -s -o data.json URL` exits 0 and data.json exists with content in it. Conclusion drawn: the fetch succeeded. actually The server answered 404 or 500. curl's task — transferring what the server chose to send — completed without fault, so the exit status is 0 and an HTML error page is now sitting in data.json under the name of the expected document. check Ask for the status separately, or make curl care about it: `curl -s -o data.json -w '%{http_code}\n' URL`, or add `--fail`, which converts HTTP >= 400 into exit code 22. Observed on a 404: plain curl exits 0, `--fail` exits 22. NS-017 A test that does not match the discovery pattern is neither run nor reported reads as pytest exits 0 with a green summary after a new test is added. Conclusion drawn: the new test passes. actually Collection matches `test_*.py` or `*_test.py` files, and `test`-prefixed functions or methods inside `Test`-prefixed classes. A file named `tests_auth.py`, or a function named `check_expiry`, is never collected. The green result belongs entirely to the other tests. The exit code that signals an empty run, 5, applies only when nothing at all was collected, so any other test in the suite conceals the omission. check `pytest --collect-only -q | grep expiry` — prints the node id if the test was collected, prints nothing if it was not. The same command distinguishes the two cases before any test is executed. NS-018 A bare mock answers to method names the real object no longer has reads as The suite is green after a collaborator's method is renamed. Conclusion drawn: nothing depended on the old name. actually `Mock()` manufactures an attribute on first access and returns another Mock, which is callable and truthy. Code calling `client.charge_card(...)` against the double passes although the real class now exposes only `charge`. The test exercises an interface that no longer exists, and will keep passing however far the real object drifts. check Derive the double from the real class: `create_autospec(Client)` or `Mock(spec=Client)` raises AttributeError on exactly the call a bare `Mock()` accepted. Observed on 3.12: `Mock().exsits()` returns a truthy Mock; `create_autospec(Real).exsits()` raises AttributeError. NS-019 Outside strict mode MySQL stores an adjusted value and calls the statement successful reads as The INSERT returns `Query OK, 1 row affected` and the client exits 0. Conclusion drawn: the row was stored as supplied. actually With strict mode absent from sql_mode, MySQL 'inserts adjusted values for invalid or missing values and produces warnings'. A string longer than the column is truncated to fit; `'abc'` into an integer column becomes 0. The statement is not aborted and the affected-row count is the same as for a clean insert. check `SHOW WARNINGS` (or `SHOW COUNT(*) WARNINGS`) immediately after the statement, in the same session: it returns rows such as `Data truncated for column ...` only when a value was adjusted, and nothing when it was not. --- Full registry: https://verifyfirst.dev/registry.json This page: https://verifyfirst.dev/exit-code/ CC0-1.0. Every entry observed, none hypothetical.