Skip to content

Exit codes

Punix's exit codes are five classes that surface the failure mode, leaving the specific error code to the message.

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 line.

Per-command tables

punix check FILE

  • 0: clean.
  • 1: at least one located error.
  • 2: FILE 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 verify STACK [...]

The one command where 1 is not an error:

  • 0: every declared config file and pinned store path matches the current generation.
  • 1: drift found, or there is no current generation for the stack. The per-row output distinguishes them.
  • 2: usage error (both --target and --transport-root, malformed target).

So punix verify Stack && echo "clean" is a valid check in a script; a non-zero exit means "go look" rather than "the command failed".

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 review hints and do not indicate failure.
  • 1: SRC 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