Exit Codes
Every sow command exits with one of seven codes. They are stable, they are the same for
every command, and they are meant to be branched on in scripts — the distinction between
“this failed” and “this was correctly refused” is the whole point of having more than one
nonzero code.
| Code | Meaning |
|---|---|
0 |
Complete success, or an idempotent no-op |
1 |
Runtime I/O, parser, renderer, or unknown internal error |
2 |
Usage, workspace discovery, or configuration error |
3 |
Partial success: at least one item committed, at least one failed |
4 |
Write lock unavailable — held and --no-wait, or the timeout expired |
5 |
Integrity or recovery error, or check judged the result not deliverable |
6 |
Expected rejection: conflict, protected, no match, incompatible architecture |
Human-readable results go to stdout; warnings and diagnostics go to stderr. Each code has
a stable message prefix on stderr, and a matching class in
JSON output:
| Code | stderr prefix | JSON class |
|---|---|---|
1 |
varies by subsystem | runtime |
2 |
usage error: / workspace discovery error: / configuration error: |
usage |
3 |
... batch partially succeeded |
partial |
4 |
lock unavailable: |
lock |
5 |
integrity or recovery error: |
integrity |
6 |
operation rejected: |
rejected |
sow create is the exception to the prefix column: being outside the managed layer, it
prints its raw domain error on stderr (plain: scan …, plain: marker gate …) without the
operation rejected: prefix. The prefix is still present in its JSON errors[].message.
0 — Success or no-op
The command did what you asked, or found there was nothing to do. Both are success:
re-running sow create over an unchanged directory, or sow build on a clean repository,
exits 0 and says so.
The "noop":true is how you tell a no-op from real work; the exit code does not
distinguish them.
sow status is a deliberate special case. As long as the state database is readable it
exits 0 in every repository state — clean, dirty, recovering, and error alike —
so a script can read the structured state instead of decoding an exit code. Use
sow check when you want a gate.
1 — Runtime error
Something went wrong at the I/O, parsing, or rendering layer: a directory that cannot be written, a disk that filled up, a package that cannot be read. These are environment problems, not usage problems.
The staging directory is created up front precisely so this fails before anything is published. A repository that already had valid indexes still has them.
2 — Usage, discovery, or configuration
You asked for something the CLI cannot act on: an unknown flag, an ambiguous target, no
workspace, or a sow.yml that does not parse. Nothing was attempted.
An unknown option:
Mutually exclusive options:
An ambiguous target — the repository has two distributions and the command needs one:
No workspace anywhere above the current directory — the message names where it searched and how to fix it:
A start directory that is not a real directory — a symlink, for instance, which is what
/tmp is on macOS — is refused before the search even begins:
A malformed configuration file — note that the offending line is named:
Every syntax and schema error in sow.yml lands here.
3 — Partial success
A batch where some items were committed and some failed. This code exists so you never
have to guess whether a failed sow add left the repository untouched: with 3, the
valid packages are in, and the failed ones are named.
The failed input file is left exactly where it was. With --json, the committed items are
still listed in full — a nonzero exit never truncates the result:
sow init uses the same code when it commits some declared repositories or distributions
and then fails on a later one.
4 — Lock unavailable
Another process holds the write lock. SOW is single-writer by design, so this is a normal, expected outcome — retry, or wait longer.
With --no-wait, the failure is immediate:
With a timeout, it fails after exactly that long:
-T 0 — the default — waits indefinitely. Read-only commands never take a write lock and
never return 4; sow status even reports the contention as a field:
5 — Integrity, recovery, or not deliverable
Two different situations share this code, and both mean “do not ship this tree yet”.
The common one is sow check on a repository whose desired state is ahead of what was
built — after sow add --skip, or after a policy or signing change. Every layer passes;
the repository is simply not converged:
The fix is sow build. This is the code a deploy script should gate on — it is the
difference between “the tree on disk is complete and current” and “the tree on disk is
complete but stale”.
The rarer situation is genuine integrity failure: a state database, journal, and file tree
that contradict each other in a way SOW cannot safely resolve on its own. It refuses to
overwrite anything, and you restore from backup rather than forcing a repair. There is no
--force here on purpose.
6 — Expected rejection
The command was well-formed, the environment was fine, and SOW decided the answer is no. These are policy and safety decisions, not failures.
A protected repository:
A reference that matches nothing:
An ambiguous bare name, with the candidates listed so you can pick one:
An architecture the workspace does not allow. Note that the per-item message names the detected value and tells you where to change it:
A directory with nothing to index:
A --pigsty completion marker guarding an existing build:
A signing key reference that parses but does not resolve — syntax errors are 2,
resolution failures are 6:
Using them in scripts
The codes are designed so a deploy pipeline can branch without parsing text:
Here mirror is a configured publication target for pigsty.
Two habits worth keeping: treat 4 as retryable rather than fatal, and never treat 6 as
a crash — it usually means your input, not SOW, needs to change.
See also
- JSON Output — the
errorsarray and itsclassfield sow check— the ordered verification layers behind code5- Transactions & Recovery — what
recoveringanderrormean