Skip to content

Exit codes

gplay's exit codes are semantic: the code alone tells a script or an agent whether retrying can help, without parsing error messages.

CodeMeaningRetry-safe?
0Success
1Generic error (fallback when nothing more specific fits)No
2CLI misuse — unknown flag, bad value, missing required argumentNo
3Safety flag required — the command is well-formed but a named acknowledgment flag (--confirm / --grant-admin) is missing; the error names itDeterministic: re-run with the named flag
4Denied by environment policy — a mutating command was refused because GPLAY_READONLY is set; the message names the env varNo — not fixable by a flag; change the environment
10Authentication failure — service account invalid, token refused, scope missingNo
11Authorization — HTTP 403, e.g. the service account was never invited on the appNo
20Client-side validation — malformed AAB, unknown locale, oversized listing textNo
30API 4xx other than auth/permissions — not found, conflict, goneNo
40API 5xx — upstream temporarily unhealthyYes
50Network — timeout, DNS, connection refusedYes
60State conflict — another Edit open, rate-limited, ambiguous release targetSometimes

The same table ships inside the binary: gplay help exit-codes is generated from the same source the binary actually returns, so it can never drift.

The transient classes (40, 50, and a rate-limited 60) are exactly what the global --retry N flag handles for you, so you rarely need a hand-rolled loop:

Terminal window
# Retry transient failures up to 3 times with exponential backoff + jitter
# (429 honors Retry-After). With --retry, --timeout bounds each attempt.
gplay releases upload app.aab --track internal --retry 3 --timeout 2m

--retry defaults to 0 (no retry). It never retries non-transient 4xx (auth, validation) or edits.commit — a duplicate commit could double-publish — so it is safe to leave on. --timeout defaults to 60s for control-plane calls and is unbounded for uploads; with --retry it becomes a per-attempt bound. A deadline-exceeded failure maps to exit 50, so the same wrapper that retries a network blip retries a timeout.

When you need shell-level control — retrying across separate commands, or adding alerting — branch on the exit code yourself:

Terminal window
for attempt in 1 2 3; do
gplay releases upload app.aab --track internal
code=$?
case $code in
0) exit 0 ;;
40|50) echo "transient (exit $code), retrying..."; sleep $((attempt * 10)) ;;
*) exit $code ;;
esac
done
exit 1

Code 3 is designed for agents: the command was valid, but a deliberate safety acknowledgment is missing (for example --confirm on a production publish, or --grant-admin when granting admin). The error message names the exact flag, so an automated caller can decide to re-run with it — a resolvable refusal rather than a dead end.

Code 4 is the opposite of 3: it is not resolvable by adding a flag. A mutating command was refused because GPLAY_READONLY is set in the environment — an authority boundary a harness can impose on an agent independent of the flags the agent chooses. The fix is to change the environment, not the command. See gplay for AI agents.