Exit codes
| Code | Meaning |
|---|---|
0 |
Did what you asked. |
1 |
Ran and failed — a rejected request, an unreachable host, a workflow file that would not parse. |
2 |
Usage error: clap rejected the invocation before anything ran. Unknown subcommand, missing required argument, a value outside an enum. |
The 1/2 split is the point of the table. A wrapper script keys on 2 to tell
“you typed it wrong” from “it tried and failed”, so it is a contract rather
than whatever the argument parser happened to return, and it is pinned by a
test that drives the built binary.
neo pr wait adds five codes above that range, one per terminal state of a
pull request, because the whole reason to have it is that a shell loop around
neo pr view had to grep prose to tell these apart:
| Code | Meaning |
|---|---|
3 |
A check on the PR’s head failed. The failing contexts are listed on stdout. --for checks only. |
4 |
The PR was closed without merging. |
5 |
Every check finished and the merge gate still refuses, so the PR will not merge without someone acting — a stale head under strict_up_to_date, a missing approval, an unresolved thread, a draft. The server’s own reasons are printed verbatim. --for merged only. |
6 |
--timeout elapsed with no terminal state. |
7 |
Every check on the head finished and every one was skipped, so nothing actually ran. Not a pass, and nothing failed. --for checks only. |
They start at 3 rather than reusing 1 on purpose: a wait that could not
reach the forge is not a red check, and a wrapper keying on 3 must not fire
on an unplugged network cable. neo actions watch predates this and still
collapses a failed run onto 1.
Two things worth knowing about the split between 3 and 5:
3is--for checksonly. “Is anything red?” is a question the client can answer completely — it folds both check sources the merge gate judges, workflow runs and externally-posted commit statuses. “Is this red check the reason the merge is blocked?” is not: which contexts a branch REQUIRES is protection config the client cannot read, so a red optional lint must never be reported as the blocker on a PR the gate is refusing for a missing approval. Under--for mergeda gate refusal is always5, carrying the gate’s own reasons, with any red contexts shown underneath as detail.5has one documented blind spot. The gate scores a required context that has reported nothing as pending, and a context with no row in either source is invisible to the client. So a gate blocked solely on a required context that has never posted looks, from outside, like a gate blocked on something human, and is reported as5early. Every other self-resolving blocker is visible: a pending external status is a real row and is counted.5is confirmed by a second independent read before it is reported, which guards a torn read across the three GETs one poll makes — it does not close this blind spot, and does not claim to.
Two more commands add a rule of their own:
neo doctorexits non-zero only when something breaks the core forge workflow. Missingdocker,nixorjjis a warning, because onlyneo runneeds them — someone who files issues and opens pull requests must never see a red report for tools they do not use.neo repo createis idempotent: a repository that already exists is reported as such and exits0.