Skip to content

Exit codes

Punix's exit codes are deliberately coarse — five classes that surface the failure mode, not the specific error code.

Code Class Triggered by
0 OK Command succeeded.
1 Located / domain error Any [E1][E15] (located, coded), or a deploy-layer domain error — ConfigPathError (path confinement), ArtifactError (cross-host artifact), FleetError, TlsError, StackComposeError — surfaced as error: <message>.
2 Usage error Bad CLI flag, missing required arg, mutually-exclusive flags both set, FILE unreadable.
3 Realise / deploy error Build failure (sandbox/recipe exit), transport error (ssh failed to connect), filesystem error during a real deploy.
>3 Internal Assertion failure, unhandled exception, harness bug. File this as an issue.

Why these classes?

Three orthogonal axes:

  • Domain vs system. 1 is the user's config or environment; 3 is the host. 2 is "you didn't tell me right."
  • Recoverable vs not. 1 is fully recoverable (fix the PCL or set the env). 3 may be recoverable (retry the build) or may require operator action (the disk filled up). >3 is a bug.
  • Located vs not. 1 ALWAYS comes with a file:line:col [E#]. 2/3 carry a clear message but the "location" is usually the command, not a file.

Per-command tables

punix check FILE

  • 0 — clean.
  • 1 — at least one located error.
  • 2FILE not a file or directory; permission denied; etc.

punix build FILE

  • 0 — every package realised successfully.
  • 1 — whole-program defect (parse / type / build-closure) — no packages built.
  • 3 — at least one package realise failed. Per-package output names the failures; see logs at ~/.punix/punix-build-logs/<MODULE>.log.

punix service deploy STACK --file PCL [...]

  • 0 — deployed; current flipped.
  • 1 — located [E#] (parse/type/closure/E11/E13/E14).
  • 2 — usage (missing --file, --target + --transport-root together, malformed --target URL).
  • 3 — transport / realise failure during the deploy. The previous generation stays live — the atomic-flip invariant.

punix service rollback STACK [...]

  • 0 — flipped; current now at gen-(prev).
  • 1[E12] (no previous gen, or pinned-path missing).
  • 2 — usage error.
  • 3 — transport failure (e.g. ssh disconnect during the flip).

punix fleet apply FLEET [...] / punix fleet rollback FLEET [...]

  • 0 — every host succeeded (N/N host(s) deployed).
  • 1 — a deploy-layer domain error before any host ran (e.g. ConfigPathError, a cross-host ArtifactError, or an artifact-graph cycle / FleetError).
  • 2 — usage error (missing --file/--key, malformed target).
  • 3 — at least one host failed; the per-host summary names ok / failed / blocked (an upstream producer failed) / not-attempted (--fail-fast).

punix store gc

  • 0 — GC complete; output names what was removed.
  • 2 — usage (--store-root doesn't exist).
  • 3 — filesystem error (permission denied removing a path).

punix migrate SRC OUT

  • 0 — migration complete. Output may contain # TODO[migrate] comments — those are NOT errors, just review hints.
  • 1SRC not a directory; rcl CLI missing; etc.

What >3 means

Reserved for internal failures. If you see exit 5, 6, or any code

3:

  • It's a Punix bug. Please file an issue with the command, the stderr, and ~/.punix/punix-build-logs/<MODULE>.log if a build was involved.
  • The harness # noqa: BLE001 safety net catches uncaught exceptions in cli.py; the corresponding exit is > 3 by convention.

Tooling integration

Exit-code-aware scripts:

# Bail out early if PCL doesn't type-check
punix check pkgs/ || { echo "fix PCL first"; exit 1; }

# Build, then deploy only if every package succeeded
punix build pkgs/ && \
  punix service deploy MyStack --file pkgs/

# Distinguish "missing secret" (recoverable) from "transport failed"
# (escalate)
if ! punix service deploy MyStack --file pkgs/ --target ssh://prod; then
  case $? in
    1) echo "Located error — check the message above and fix" ;;
    3) echo "Transport failure — paging on-call" ;;
    *) echo "Unexpected — file an issue" ;;
  esac
fi