Kuberwastaken/claurst · error · anyhow::Error
Bridge poll: auth error
Error message
Bridge poll: auth error ({}) What it means
After a successful token-exchange HTTP response, `exchange_code` deserializes the body into its internal `TokenResponse` struct (access_token, refresh_token, expires_in, scope). If the body is not valid JSON or does not match the expected shape, the serde/reqwest error is wrapped as "exchange_code: bad JSON: {}". The token endpoint returned 2xx but not the JSON the library expects.
Solutions
- Capture and inspect the raw response body (curl the token endpoint with the same form payload) to see what was actually returned.
- Verify the token_endpoint URL is correct and returns `application/json` for the authorization-code grant.
- If behind a proxy/captive portal, fix the network path so the real provider response arrives.
- If the provider uses nonstandard types, file/track support for that provider's wire format rather than re-running the flow.
Defensive patterns
Strategy: try-catch
Try / catch
// on "exchange_code: bad JSON", log the raw body to diagnose
Err(e) if e.to_string().contains("bad JSON") => {
eprintln!("Token endpoint returned non-JSON; verify token_endpoint and network path: {}", e);
Err(e)
} Prevention
- Curl the token endpoint with the same form payload to confirm it returns application/json
- Ensure no captive portal/proxy injects HTML 200 responses
- Confirm the token_endpoint from provider metadata is current
When it happens
Trigger: `resp.json::<TokenResponse>()` fails — the endpoint returned HTML (e.g. an error page or login page behind a 200), an empty body, or JSON whose types differ (e.g. expires_in as string instead of u64).
Common situations: A reverse proxy or captive portal returning an HTML 200 page; the provider returning `application/x-www-form-urlencoded` instead of JSON for token responses; nonstandard field types (string "3600" for expires_in); token_endpoint pointing at the wrong URL.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- No access_token in response
- Failed to parse token response
- OAuth metadata parse error
- models endpoint returned
- Invalid JWT: expected at least 2 dot-separated segments
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/36a7995094b4b8ae.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/bridge/src/lib.rs:580
.send()
.await
.context("Bridge poll: HTTP send failed")?;
let status = resp.status().as_u16();
match status {
200 => {
let text = resp.text().await.context("Bridge poll: reading body")?;
if text.trim().is_empty() || text.trim() == "[]" {
return Ok(vec![]);
}
let msgs: Vec<BridgeMessage> =
serde_json::from_str(&text).context("Bridge poll: JSON parse")?;
Ok(msgs)
}
204 => Ok(vec![]),
401 | 403 => {
self.set_state(BridgeState::Error(format!("Auth error: {status}")));
anyhow::bail!("Bridge poll: auth error ({})", status)
}
_ => {
anyhow::bail!("Bridge poll: server returned {}", status)
}
}
}
// -----------------------------------------------------------------------
// Event upload
// -----------------------------------------------------------------------
/// Batch-upload outgoing events to the web UI.
///
/// POST `/api/claude_code/sessions/{id}/events`
async fn upload_events(&self, events: Vec<BridgeEvent>) -> anyhow::Result<()> {
if events.is_empty() {
return Ok(());
}
View on GitHub (pinned to b0637c97ec)