Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Exit codes

KeyHog uses exit codes to signal scan outcomes. Stable across versions; consumers (CI gates, pre-commit hooks, IDE plugins) can rely on them.

ExitMeaning
0Scan completed, zero findings.
1Findings present, NONE confirmed live (unverified, or verified-dead).
2User error: unknown CLI flag, .keyhog.toml parse failure, bad --baseline.
3System error: I/O failure, source-backend failure, or detector-corpus audit failure.
4Health/self-test failure: keyhog doctor unhealthy, keyhog repair could not restore a working binary, keyhog backend self-test failed.
10LIVE credentials confirmed (a --verify scan where the vendor API accepted a found secret) - the highest-severity gate. Also returned by keyhog update --check when a newer release exists.
11Scanner thread panicked. The finding count is NOT trustworthy - investigate, don’t ship. Distinct from 2/3 so CI can tell a code bug from a config error.
130Interrupted (SIGINT / Ctrl-C).

0 (clean)

Use case: a CI step like keyhog scan . exits 0 when the working tree is clean. The job stays green.

With --verify, the exit code escalates when a credential is confirmed live: a found secret the vendor API accepts exits 10, while a found secret that verifies dead (or wasn’t verified) exits 1. So gating ONLY on live credentials needs no JSON parsing - branch on the exit code:

keyhog scan . --verify
case $? in
  0)  echo "clean" ;;
  10) echo "LIVE credentials present - block + page" ; exit 1 ;;
  1)  echo "findings, none confirmed live" ;;
esac

1 (findings present)

The most common non-zero. CI fails, pre-commit hook blocks the commit, PR check turns red. Findings get printed to stdout in whatever format --format selected.

Exit 1 means findings exist but, under --verify, none were confirmed live. A scan that confirms a live credential exits 10 instead (see below) - so “findings but all dead” vs “some live” is just 1 vs 10, no JSON parsing required.

2 (runtime error)

Things that exit 2:

  • Unknown CLI flag.
  • .keyhog.toml parse error.
  • Detector load failure for a specific TOML (with a stderr warning; the rest of the scan continues but exits 2 at the end).
  • --baseline <FILE> where FILE doesn’t exist or isn’t valid JSON.
  • A source backend failure (e.g. --git-history on a non-git dir).
  • Network error during --verify is NOT a 2; it’s a verification-error marker per finding and the scan exits 1 if any unverified-live findings exist.

Stderr carries the error message. Stdout may have partial output depending on where the error happened.

3 (system error)

A failure the operator can’t fix by correcting a flag: an I/O error, a source backend that couldn’t read its input, or a detector-corpus audit failure. Distinct from 2 (user error) so a pipeline can retry/route differently. Stderr carries the cause.

4 (health / self-test failure)

Returned by the maintenance subcommands, not by scan: keyhog doctor when the install fails its end-to-end self-test, keyhog repair when it could not restore a working binary, and keyhog backend when its self-test fails. A health monitor can treat 4 as “binary present but not trustworthy.”

10 (live credentials, or update available)

The highest-severity scan outcome: a --verify scan where the vendor API accepted a found secret - it is real and exfil-capable right now. Gate hard on this:

keyhog scan . --verify || rc=$?
[ "${rc:-0}" = "10" ] && { echo "::error::live credential confirmed"; exit 1; }

keyhog update --check reuses 10 to mean “a newer release exists” (exit 0 = already current), so a self-update cron can branch on it.

11 (scanner panic)

A panic inside a scanner thread (regex compile bug, OOM in a windowed chunk, etc.). The scan was incomplete; the count of findings emitted is NOT trustworthy. CI should treat this as “investigate” rather than “ship anyway because exit 11 != 1”.

The reason this is 11 rather than 2:

  • A panic is a code bug worth surfacing distinctly.
  • Some CIs (older Jenkins, certain shell wrappers) collapse 2 with “command not found” or other ambient errors. 11 is unambiguous.
  • A future expansion of error categories (12 = OOM-killed, 13 = timeout-exceeded, etc.) is possible without renumbering existing codes.

Composing in shell

set -e
keyhog scan .                # exit 1 stops the shell here

Or to handle the non-zero explicitly:

keyhog scan . --verify || rc=$?
case "$rc" in
  0|"")  echo "clean" ;;
  1)     echo "findings (none live) -> opening PR comment" ;;
  10)    echo "LIVE credentials -> block + page on-call" ;;
  2)     echo "user error (bad flag/config) -> failing build" ;;
  3)     echo "system error -> retry / investigate" ;;
  11)    echo "scanner panic -> paging on-call" ;;
  130)   echo "interrupted" ;;
  *)     echo "unknown exit $rc" ;;
esac

What you can’t do

  • No --exit-zero flag. KeyHog deliberately does not provide a way to lie to CI about findings. If you need to override (e.g. “this finding is accepted, ship anyway”), suppress it by hash in .keyhog.toml instead. The exit code then reflects truth: there are no UN-suppressed findings, so it’s 0.