Relationships
#1477 serve oauth: startDeviceGrant discards the OAuth error field, making device-code failures undiagnosable
Opened by keeb · 7/30/2026· Shipped 7/30/2026
Summary
startDeviceGrant (src/serve/oauth_client.ts:87-91) interpolates only
resp.status and resp.statusText when POST /api/auth/device/code fails,
dropping the OAuth error field from the response body. Every server-side
rejection of a device authorization request therefore reaches the operative as
the same opaque line:
[FTL] error: Error: Device authorization request failed: 400 Bad Request400 Bad Request cannot distinguish invalid_client from invalid_request
from a generic VALIDATION_ERROR, so the log tells you the request failed but
nothing about why.
Why it matters
This is what made swamp-club lab #1476 expensive to diagnose. A customer
deployment could not complete swamp serve --auth-mode oauth first-boot
bootstrap; the only signal was the line above. Identifying the cause required
reading swamp-club server source and better-auths compiled plugin routes to learn that the server was answering invalid_client`. The body was there in the
response all along — the CLI threw it away.
Both siblings already do this correctly
pollForToken(src/serve/oauth_client.ts:127-137) reads the body, checksdata.erroragainstKNOWN_POLL_ERRORS, and surfaces${resp.status} ${data.error ?? resp.statusText}.- The
/registercall insrc/cli/commands/serve.ts:1220-1227readsawait resp.text()and appends it to the message.
startDeviceGrant is the odd one out.
Steps to reproduce
- Point
swamp serve --auth-mode oauthat a provider that rejects the bootstrap client id (any 400 from/api/auth/device/codewill do). - Observe the log line:
Device authorization request failed: 400 Bad Request. - Note that the OAuth
error/error_descriptionfields the server sent are nowhere in the output.
Proposed fix
Mirror pollForToken:
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(
`Device authorization request failed: ${resp.status} ${
data.error ?? resp.statusText
}${data.error_description ? ` — ${data.error_description}` : ""}`,
);
}Origin
Found while fixing swamp-club lab #1476 (device flow rate-limited to a
45-second window by the server-side validateClient counter). That root cause
is fixed on the swamp-club side; this is the separate CLI-side diagnosability
gap it exposed.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.