Skip to main content
← Back to list
01Issue
BugShippedSwamp CLI
Assigneesstack72

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 Request

400 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, checks data.error against KNOWN_POLL_ERRORS, and surfaces ${resp.status} ${data.error ?? resp.statusText}.
  • The /register call in src/cli/commands/serve.ts:1220-1227 reads await resp.text() and appends it to the message.

startDeviceGrant is the odd one out.

Steps to reproduce

  1. Point swamp serve --auth-mode oauth at a provider that rejects the bootstrap client id (any 400 from /api/auth/device/code will do).
  2. Observe the log line: Device authorization request failed: 400 Bad Request.
  3. Note that the OAuth error / error_description fields 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.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 4 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/30/2026, 4:40:47 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack727/30/2026, 1:16:24 PM

Sign in to post a ripple.