Relationships
#1478 serve oauth: an unrecognized device-token error kills the whole serve process instead of the bootstrap
Opened by keeb · 7/30/2026· Shipped 7/30/2026
Summary
A mid-flow rejection from POST /api/auth/device/token terminates
swamp serve outright rather than surfacing as a handled bootstrap failure.
Two gaps combine:
KNOWN_POLL_ERRORS(src/serve/oauth_client.ts:69-74) lists onlyauthorization_pending,slow_down,expired_token, andaccess_denied. Anything else —invalid_grant,invalid_client,server_error— falls through tothrow new Error(...)atoauth_client.ts:135-137instead of a typedDeviceGrantPollError.- The bootstrap retry loop (
src/cli/commands/serve.ts:1171-1198) only swallowsDeviceGrantPollError, so a plainErrorescapes atserve.ts:1196. There is no try/catch aroundresolveOAuthClientCredentials(serve.ts:1134), so the process exits FTL.
Net effect: any unexpected answer from the token endpoint during first-boot bootstrap takes the whole server down, mid-poll, with no chance to report a usable error or retry.
Why it matters
This is the mechanism that turned swamp-club lab #1476 from "bootstrap failed"
into "the server died." The server started answering
400 invalid_grant "Invalid client ID" partway through a conforming poll
sequence; because invalid_grant is not a known poll error, the throw escaped
and killed swamp serve. In a Kubernetes deployment that reads as a crashloop
rather than a configuration problem.
Fixing the server-side root cause (done, swamp-club side) removes this particular trigger. It does not remove the fragility: any future token-endpoint rejection the CLI does not enumerate will do the same thing.
Steps to reproduce
- Start
swamp serve --auth-mode oauthagainst a provider with no stored client credentials, so it enters the bootstrap device grant. - While it polls, make the provider answer
/api/auth/device/tokenwith any error outside the four known codes (invalid_grantis the natural one — a revoked or unregistered client id will produce it). swamp serveexits:error: Error: Token request failed: 400 invalid_grant.
Expected: the bootstrap reports a clear failure (and ideally retries or exits with a diagnosable message); the process should not die inside a poll loop.
Proposed fix
Two independent changes, either of which is an improvement:
- Treat an unrecognized OAuth error as a terminal poll error rather than an
unhandled throw — e.g. extend
DeviceGrantPollErrorCodewith anunknown/invalid_grantvariant so the loop can decide, rather than the error type deciding for it. - Wrap
resolveOAuthClientCredentialsatsrc/cli/commands/serve.ts:1134in a try/catch that reports the bootstrap failure and exits deliberately, so no device-flow error can take the process down as a side effect.
Origin
Found while fixing swamp-club lab #1476 (device flow rate-limited to a
45-second window by the server-side validateClient counter). Sibling of
#1477 (startDeviceGrant discards the OAuth error field).
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.