CLI exit codes and failure output
The exit status of airdress and its failure report are a stable contract:
scripts, the deploy-functions GitHub Action, the editor extension and MCP
clients read them. A code is never renumbered or reused for another
meaning; a new kind of failure gets a new number.
Exit codes
Section titled “Exit codes”| Code | Name | Meaning | Typical causes |
|---|---|---|---|
0 | ok | The command did what it was asked. | Includes airdress update --check when an update is available. |
1 | internal | Unexpected: a bug, an answer the CLI could not read, a local file it could not read or write, a 5xx from the server itself. | 500, a malformed body, a disk error. |
2 | usage | The command line is wrong. | An unknown subcommand or flag, a missing argument, a missing signing key (signer_unavailable). |
3 | refused | Your airdress or the hub refused the request, or a person declined at a prompt. | 403, 404, 422, 429, a dry run’s refusal, capability_not_granted, confirmation_declined. |
4 | auth | Authentication is needed or has expired. | No profile, no sign-in, a refresh the hub refused, 401, machine_authorization_expired. |
5 | network | The other end could not be reached or did not answer in time. | Connection refused, DNS failure, timeout, 408, 502, 503, 504. |
6 | conflict | The request lost a race with another writer. | 409, 412, source_base_stale, epoch_conflict, root_key_mismatch. |
7 | confirmation required | The command needs a yes and could not ask: stdin is not a terminal, or --output json is set. Pass -y / --yes. | A destructive command run from a script. |
A refusal is classified by its code first (a source_base_stale is 6
whatever its HTTP status), then by its HTTP status.
Failure output
Section titled “Failure output”Text mode (the default) writes to stderr:
Error: <what failed: its cause>hint: <the one useful next step, when there is one>--output json writes exactly one JSON object to stderr, on one line,
and nothing else for the failure:
{"code":"source_base_stale","message":"stale base: …","hint":"re-base the tree …","status":409}| Field | Always | Meaning |
|---|---|---|
code | yes | A stable snake_case identifier: your airdress’s own code verbatim (epoch_conflict, publication_blocked), or one of the CLI’s (internal, usage, auth_required, not_signed_in, sign_in_expired, profile_not_found, unreachable, timeout, confirmation_required, http_<status>). A script decides on code and the exit status, never on message. |
message | yes | For a person. May change between releases. |
hint | no | What to do about it. |
status | no | The HTTP status that was answered, when there was one. |
| anything else | no | Every other field of the refusal (reason, locations, denials, basedOn, current, …), carried through unchanged. |
A command that prints a structured result on stdout (a deploy’s reports, a
refusal of fn publish) still does; the failure object on stderr is in
addition to it.
airdress fn deploy ./hello --yes --output json >deploy.json 2>err.jsoncase $? in 0) echo "deployed" ;; 4) airdress auth login ;; 6) echo "someone else deployed first: $(jq -r .current err.json)" ;; *) jq -r '.code + ": " + .message' err.json ;;esacairdress update --check
Section titled “airdress update --check”Exits 0 whether or not an update is available. --check is a question,
and answering it is success; the answer is in the output (Update available: a -> b, or "update_available": true under --output json). A non-zero exit
means the check itself failed, which is the case a script most needs to
notice.
Exceptions
Section titled “Exceptions”airdress shellends with the remote program’s own exit status, passed through unchanged, assshdoes. A failure of the shell client itself (before the program ran, or a lost session) uses the codes above, but a remote program may exit with any of1to255, including those numbers.- A panic in a serving mode (
shell host,mcp serve, andairdress-agent device serve) exits101, Rust’s own code for a panic. - Help and
--versionexit0; asking for help is not a failure.
Confirmation prompts
Section titled “Confirmation prompts”Every command that changes something it cannot undo asks first, through one
prompt, and every one of them takes -y / --yes: device revoke,
fn deploy, fn signers add|remove, home disconnect,
plugins install|uninstall|deauthorize, shell close,
shell device forget.
- With
-y/--yesit does not ask. - Under
--output json, or when stdin is not a terminal, it never asks: it exits7withconfirmation_required, a hint naming--yes, and"flag": "--yes"in the JSON object. - Otherwise it asks
… [y/N]on stderr. The default is No: onlyyoryes(any case) confirms; anything else, an empty line or end of input, is a decline, exit3withconfirmation_declined.
Two questions only a person can answer, so no flag answers them and they
exit 7 without a terminal: shell host trust (trusting a device by its
fingerprint) and a shell host’s first-use key (“Does it match?”), including
the join question asked inside airdress shell.
Related
Section titled “Related”- Scripting with the CLI:
--output json, tokens and environment variables. - Error reference: what the common refusal codes mean.