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 six 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. |
8 |
The PR is not being tested on a merge queue candidate (it left the queue, or the queue gates on its head), nothing on the head is red or still running, and some head checks were superseded: an earlier candidate’s runs replaced them and the queue cancelled them. They will never finish — re-run them, or wait on the queue with --for merged. While the queue tests a candidate, --for checks judges the candidate’s checks instead and never ends here. --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.
Six 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. An available upgrade is a warning too.neo repo createis idempotent: a repository that already exists is reported as such and exits0. So isneo org claimfor an organization you are already a member of.neo org claimexits1whenever the wait ends with the organization still not yours.--timeoutelapsing is one way: that is not a verdict — the claim may still be in progress in the browser, or it was refused there, and the command says so. A wait that ends with the organization visible to you but not as its owner is another: someone added you as a member while it waited, and the claim itself did not complete. And a wait whose last read failed — a revoked credential, an unreachable host — reports that failure rather than a claim in progress.neo upgrade --checkexits1when an upgrade is available and0when the running build is the published one, so a script can key on it without parsing prose. A failure to find out — no receipt, an unreachable release host — is the ordinary1as well;--jsontells the two apart.neo repo github planexits1when the attach preview lists blockers, after printing each one with its fix code and link;--jsonkeeps that code (the raw response is still printed). A repository with no attach run, or a plan still being computed, exits0.neo repo github statusis a report: it exits0with blockers listed, and0for a repository with no GitHub relationship.