{"record":{"id":"4860adcca7836e5e","repo":"Hmbown/CodeWhale","slug":"name-oauth-operation-returned-http-status-that-was-not-token","errorCode":null,"errorMessage":"{name} OAuth {operation} returned HTTP {status} that was not token JSON","messagePattern":"(.+?) OAuth (.+?) returned HTTP (.+?) that was not token JSON","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":974,"sourceCode":"/// Pinned remote revoke URL; `None` when the provider revokes locally only.\npub(crate) fn remote_revoke_url(params: &OAuthProviderParams, issuer: &str) -> Option<String> {\n    params\n        .revoke_path\n        .map(|path| format!(\"{}/{}\", issuer.trim_end_matches('/'), path))\n}\n\n/// Parse a form-post token response. Error bodies are never echoed: the\n/// detail names the error code only, so a hostile issuer cannot smuggle\n/// secret-bearing text back through diagnostics.\npub(crate) fn parse_oauth_form_response(\n    status: u16,\n    body: &str,\n    operation: &str,\n    params: &OAuthProviderParams,\n) -> Result<OAuthTokenMaterial> {\n    let name = params.display_name;\n    let parsed: OAuthTokenMaterial = serde_json::from_str(body).map_err(|_| {\n        anyhow::anyhow!(\"{name} OAuth {operation} returned HTTP {status} that was not token JSON\")\n    })?;\n    if !(200..300).contains(&status) || parsed.error.is_some() {\n        let err = parsed.error.as_deref().unwrap_or(\"token_error\");\n        if matches!(\n            err,\n            \"invalid_grant\"\n                | \"refresh_token_reused\"\n                | \"refresh_token_expired\"\n                | \"refresh_token_invalidated\"\n        ) || status == 401\n        {\n            bail!(\n                \"{name} OAuth {operation} failed permanently ({err}). Sign in again with `{}`.\",\n                params.relogin_hint\n            );\n        }\n        bail!(\"{name} OAuth {operation} failed ({err})\");\n    }","sourceCodeStart":956,"sourceCodeEnd":992,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L956-L992","documentation":"This path parses an OAuth token-endpoint response body into `OAuthTokenMaterial` (name and operation are interpolated from the provider params for a clear message). If the body is not valid JSON, or lacks the token structure, the error states the provider name, operation, and HTTP status so the developer knows which request went wrong. It distinguishes 'not token JSON at all' from the structured token-error handling that follows.","triggerScenarios":"A custom OAuth provider's token endpoint returns a non-JSON body (HTML login page, empty body, XML) for a token or refresh operation; a device-flow token poll receives an intermediate non-JSON response.","commonSituations":"Provider params misconfigured so the token URL hits a human-facing sign-in page; an OAuth server that responds to authorization_code exchange with a redirect/HTML; a captive portal replacing the response.","solutions":["Verify the token URL in the provider params points at the real token endpoint (usually .../token), not the authorize or documentation URL","curl the token endpoint with a test request and confirm it returns application/json token data","Check the HTTP status in the message: an HTML 200 from the wrong URL is a config error; 5xx is a server-side problem"],"exampleFix":"// before: token_url points at authorize page\n\"token_url\": \"https://idp.example.com/authorize\"\n// after\n\"token_url\": \"https://idp.example.com/oauth/token\"","handlingStrategy":"try-catch","validationCode":"async fn token_endpoint_returns_json(url: &str) -> anyhow::Result<()> {\n    let body = reqwest::get(url).await?.text().await?;\n    if serde_json::from_str::<serde_json::Value>(&body).is_err() {\n        anyhow::bail!(\"token endpoint at {url} does not return JSON\");\n    }\n    Ok(\"\")\n}","typeGuard":null,"tryCatchPattern":"match exchange_code_for_token(params, code) {\n    Ok(material) => material,\n    Err(e) if e.to_string().contains(\"not token JSON\") => {\n        // provider name + operation + status are in the message:\n        // usually a wrong token_url or an HTML error page\n        log_raw_response_and_fix_token_url(params)?;\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Confirm token_url points at the token endpoint, not authorize/login pages","Test the provider with curl before configuring it","Be wary of development/stub servers that return non-JSON bodies","Check provider docs for non-standard content types"],"tags":["oauth","json","token-endpoint","http"],"backgroundTag":"invalid-json-response","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}