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

Relationships

#1900 dashboard device-login poll checks for an error code serve never sends, so all failures show as an endless spinner

Opened by sntxrr · 8/28/2026· Shipped 8/30/2026

Summary

The dashboard's OAuth device-login poll tests for an error code that swamp serve never sends. As a result no failure mode is ever displayed: expiry, user denial, admission refusal and upstream errors all render identically as an indefinite "Waiting for you to approve...".

Environment

  • swamp 20260828.202201.0-sha.2a5bcb89
  • swamp serve --auth-mode oauth --dashboard

The mismatch

packages/dashboard/src/views/Login.tsx:173-178:

if (data.token) {
  onToken(data.token);
} else if (data.error === "expired_token") {
  setError("Login expired. Please try again.");
  setState("error");
}
// every other response: silently ignored, keep polling

src/serve/device_auth_handler.ts emits these, and only these:

Condition Response
pending 202 {"status":"pending"}
expired 410 {"error":"Device code expired"}
denied by user 403 {"error":"Authorization denied by user"}
not admitted 403 {"error":"Not admitted","reason":...}
upstream failure 502 {"error":"Upstream provider returned an unexpected error"}

"expired_token" is the OAuth error code from the upstream provider, which the handler translates before responding. The route in src/cli/commands/serve.ts:3734 passes the handler's response through unmodified, so the string the client checks for never appears on the wire.

The response status is also ignored — resp.ok is never consulted, so a 410, 403 or 502 is parsed as JSON and discarded exactly like a 202.

Impact

Every device-login failure looks identical to "still waiting". In particular a user who is authenticated but outside --allowed-collectives gets 403 Not admitted — the one message that would tell them exactly what is wrong — and sees a spinner instead, indefinitely.

This was hit in practice while diagnosing a server-side crash: the server was aborting mid-flow and the dashboard reported nothing at all, which made the crash considerably harder to find.

Suggested fix

Branch on the HTTP status rather than an error string, and surface data.error / data.reason verbatim. Something like:

  • 202 → keep polling
  • 403 → show data.error plus data.reason when present (terminal)
  • 410 → "Login expired, please try again" (terminal)
  • 5xx → show the error and offer retry (terminal)

Continuing to poll should be the explicitly-handled case, not the default for everything unrecognised.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 8 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

8/30/2026, 3:41:02 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack728/30/2026, 3:03:10 AM
Editable. Press Enter to edit.

stack72 commented 8/30/2026, 3:41:10 AM

Thanks @sntxrr for reporting this! The fix has been merged and a release is on its way. We appreciate your contribution to swamp.

Sign in to post a ripple.