SDK and error contracts
HTTP status and exit code
HTTP status and exit code Two derivations turn a code into numbers the outside world reads: the HTTP status a service answers with at its edge, and the exit code a command leaves the process with. Both live in the catalogue ( http status pe
HTTP status and exit code
Two derivations turn a code into numbers the outside world reads:
the HTTP status a service answers with at its edge, and the exit code a
command leaves the process with. Both live in the catalogue (http_status
per code; the exit_code block), both are generated into every runtime, and
both arrived here from three identical hand-kept copies each — the
catalogue's provenance field names the exact files.
There is also a third, inbound mapping: classifying the status an upstream answered one of our calls with. All three are on this page because they are the three places a number and a code meet.
Edge HTTP status: code → status
httpStatus(code) / http_status(code) / code.http_status() /
code.httpStatus — what a service answers when this failure reaches its
edge.
| code | status |
|---|---|
config |
503 |
auth |
401 |
not_found |
404 |
rate_limit |
429 |
timeout |
504 |
infra_down |
503 |
unknown |
500 |
Captured from the adopted toy service in
walkthrough-adoption: an infra_down envelope
answers HTTP/1.1 503 Service Unavailable, a rate_limit one answers
HTTP/1.1 429 Too Many Requests.
Exit code: code → process exit
RETRY_EXIT is 69 — EX_UNAVAILABLE from sysexits.h, on every platform
this fleet runs on. The rule, quoted from the catalogue: "retryable codes
exit with retry; every other code keeps the exit code the caller already
chose". Exposed as exitCode(code, chosen) / exit_code(code, chosen) /
code.exit_code(chosen) / code.exitCode(chosen:).
Only the retryable path is remapped so a CLI's own exit-code conventions survive adoption — a caller that already chose 2 for "bad usage" keeps 2. Captured from the executed examples:
exit_code('infra_down', 3): 69 (RETRY_EXIT = 69)
exit_code('not_found', 3): 3
and the compiled Rust consumer in integrate/rust
really exits 69 when its failure is retryable (echo $? → 69).
Upstream status: status → code
fromUpstreamStatus / from_upstream_status / Code::from_upstream_status
/ Code.fromUpstream(status:) classify the HTTP status an upstream answered
one of our calls with, identically in all four runtimes. Exact matches are
checked before ranges; ranges are inclusive; anything unmatched is
unknown.
| status | code |
|---|---|
| 401, 403, 407 | auth |
| 404, 410 | not_found |
| 408, 504 | timeout |
| 429 | rate_limit |
| 501, 505 | config |
| 500–599 (any other) | infra_down |
| anything else | unknown |
501 and 505 sit ahead of the 5xx range on purpose. The catalogue defines
infra_down as "a dependency did not answer at all" — and a 501 or a 505
did answer. A server saying it does not implement the method, or does not
speak the version, is our deployment being incomplete: retrying cannot help,
so config is the honest code. The catalogue's upstream_status.note
records that most-server read 501 as config in two independent places before
this fix. A 5xx is infra_down and never not_found.
Two lines every hand-rolled copy of this ladder got wrong — the missing 407
branch and the unbounded >= 500 — are exactly why the classifier is owned
here (integration).
Invariants
- 200 and 600 are probed too. The conformance table classifies statuses
including 200 and 600 — neither is a failure status, both classify as
unknown, and a runtime that classifies them differently from the others is still a disagreement (tests/conformance/table.tsv). - The remap rule is visible in the probes. The table carries
chosen_exit 2, so every runtime's dump shows retryable codes answering 69 and everything else answering 2. - Derived for every code.
http_statusandexit_codeexist for all seven codes — which is the stated reason the catalogue has noofflinecode: neither derivation applies to a code no server can observe and no command can exit with (boundary).
Not to be confused with
- The status our edge answers vs the status an upstream answered us.
httpStatusis outbound (ours);fromUpstreamStatusis inbound (theirs). They are not inverses:fromUpstreamStatus(503)isinfra_down, andhttpStatus("infra_down")is 503, butfromUpstreamStatus(500)is alsoinfra_downwhilehttpStatus("unknown")is 500. - A product's other exit codes. The package owns one exit code, 69. Everything else is the caller's convention, passed through untouched.