Exit Codes
Every sow command uses exit codes 0–6 or interruption code 130. 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 |
130 |
Command interrupted by Ctrl-C (SIGINT) or SIGTERM |
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, discovery, config |
3 |
... batch partially succeeded |
partial |
4 |
lock unavailable: |
lock |
5 |
integrity or recovery error: |
integrity |
6 |
operation rejected: |
rejected |
130 |
varies by subsystem | interrupted |
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
CLI class prefix, such as operation rejected: or lock unavailable:. The prefix is still
present in its JSON errors[].message.
130 — Interrupted
Ctrl-C (SIGINT) or SIGTERM cancels the command and returns 130; ordinary lock-wait
timeout remains 4. Only the command’s own interruption returns 130: an internal timeout,
such as an R2 upload that makes no progress, is an ordinary runtime error (1).
SOW returns a numeric exit status; it does not re-send SIGINT to itself. A parent shell may
therefore continue to the next command unless the script checks the status, uses set -e,
or handles signals itself. Cancellation is not reported as a signing-policy failure. For an add cancelled before
Desired commits, no input is reported as accepted and the failure state is durable before
temporary package bytes are removed. If that cleanup does not finish within its 5-second
budget, the next write command (for example sow build) removes the rest, and sow check
reports recovering until then. An already committed change remains committed;
rerun the command to recover or converge it. Keep the original input packages until the
operation completes.
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 path that names a regular file rather than a directory is refused before the search begins:
A symlink to a directory is accepted as a discovery start, including /tmp on macOS.
Discovery still walks the literal path’s ancestors and falls back to SOW_DIR
when no workspace is found. Implicit Repository and Dist selection from the start path is
stricter: any symlink component between the start directory and the workspace root prevents
that inference, so pass -r. If the symlink is inside the Repository, a command that infers the
Dist still fails with -r alone; pass -d too.
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