Exit codes
Exit codes are a first-class contract. They are documented here, emitted by gfly schema
(as exit_codes), and guarded by a schema-snapshot test on every release. The table is
append-only: no code will ever be renumbered or removed.
Run gfly schema at any time to get the live authoritative table alongside the full command
tree and current throttle state.
Full exit-code table
Section titled “Full exit-code table”| Code | Symbolic name | When it fires |
|---|---|---|
0 |
ok |
Command succeeded. |
1 |
generic_error |
Unexpected error with no dedicated code. |
2 |
usage |
Bad flags, arguments, or dates — fails before any network call. |
3 |
empty_results |
The query was valid but returned no flights. Try broader dates, different airports, or --stops any. |
4 |
auth_required |
The serpapi backend needs an API key. Run gfly auth login or set GFLY_SERPAPI_KEY. |
5 |
not_found |
A resource could not be located — e.g. an unrecognised identifier. |
6 |
permission |
Permission denied for the requested operation. |
7 |
rate_limited |
The politeness throttle or an upstream HTTP 429 is in effect. Always carries retryAfterSeconds. |
8 |
retryable |
Transient network error; the request may succeed on a second attempt. |
10 |
config_error |
Configuration file is invalid or a required config value is missing. |
12 |
mutation_blocked |
A mutating operation was requested. Defined for contract uniformity; never fires — gfly is read-only. |
13 |
input_required |
An interactive prompt was needed but --no-input is active. Pass the value as a flag instead. |
20 |
blocked |
Google returned a CAPTCHA or soft-block; the circuit breaker is open. Always carries retryAfterSeconds. |
21 |
schema_drift |
The upstream response could not be parsed — the fast-flights engine has drifted after a Google change. Upgrade gfly or switch to --backend serpapi. |
130 |
cancelled |
Process received SIGINT (Ctrl-C / click.Abort). |
Structured error shape
Section titled “Structured error shape”When stderr is consumed by a machine (i.e. when --format json / --json is active),
errors are emitted to stderr as a single JSON object:
{ "error": "throttled; next request allowed in ~47s", "code": "RATE_LIMITED", "remediation": "wait and retry, pass --wait to block until allowed, or --backend serpapi", "retryAfterSeconds": 47}| Field | Type | Always present | Notes |
|---|---|---|---|
error |
string | yes | Human-readable message. |
code |
string | yes | Uppercase symbolic name — stable, machine-matchable. |
remediation |
string | yes | Suggested fix (may be empty string). |
retryAfterSeconds |
integer | no | Only on RATE_LIMITED (code 7) and BLOCKED (code 20). Seconds until the next request is permitted. |
Without --json, errors are printed as plain text lines to stderr in the form:
error: throttled; next request allowed in ~47s code: RATE_LIMITED fix: wait and retry, pass --wait to block until allowed, or --backend serpapi retryAfterSeconds: 47Agent patterns
Section titled “Agent patterns”Handling RATE_LIMITED and BLOCKED
Section titled “Handling RATE_LIMITED and BLOCKED”Both carry retryAfterSeconds. An agent should read that value and either:
- schedule a retry after the indicated delay, or
- switch backends immediately:
--backend serpapiis exempt from the politeness throttle entirely.
result=$(gfly search JFK LHR --json --depart 2026-09-01 2>&1)code=$(echo "$result" | jq -r '.code // empty')
if [ "$code" = "RATE_LIMITED" ] || [ "$code" = "BLOCKED" ]; then retry_after=$(echo "$result" | jq -r '.retryAfterSeconds') echo "Back off ${retry_after}s or switch to serpapi backend."fiHandling SCHEMA_DRIFT
Section titled “Handling SCHEMA_DRIFT”Code 21 means the fast-flights engine can no longer parse Google’s response — a Google
change has broken the reverse-engineered endpoint. The remediation is:
- Run
uvx gfly@latest(orpip install --upgrade gfly) to pick up a patched engine. - Switch to
--backend serpapias a reliable fallback while awaiting an engine fix. - File an issue at github.com/rnwolfe/gfly with the
SCHEMA_DRIFTdetail string.
Detecting empty vs. error
Section titled “Detecting empty vs. error”Exit code 3 (empty_results) is not an error — the command succeeded but found no
matching flights. Distinguish it from a real failure:
gfly search SFO NRT --json --depart 2026-12-01# exit 0 → flights found# exit 3 → valid query, no results (broaden search)# exit 2 → bad flags/dates (fix the command)# exit 7 → throttled (wait or switch backend)Stability guarantee
Section titled “Stability guarantee”The exit-code table is append-only:
- Existing codes never change their numeric value or symbolic name.
- New codes may be added in any release.
- The
gfly schemaoutput (fieldexit_codes) always reflects the running version’s full table — agents can ingest it at startup and rely on it for the lifetime of that installation.
See also output schema for the stable JSON envelope on stdout,
and for agents for the full agent integration guide including
SCHEMA_DRIFT handling and throttle state.