{"record":{"id":"b43355ecc3a68442","repo":"Hmbown/CodeWhale","slug":"oauth-device-code-request-failed-detail","errorCode":null,"errorMessage":"OAuth device-code request failed ({detail})","messagePattern":"OAuth device-code request failed \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":741,"sourceCode":"    let device_authorization_endpoint = oauth_endpoint_url(device_authorization_endpoint)?;\n    let client = oauth_http_client(\"device-code\")?;\n    let params = [(\"client_id\", client_id), (\"scope\", scopes)];\n    #[cfg(test)]\n    crate::external_credentials::record_oauth_network();\n    let response = client\n        .post(device_authorization_endpoint)\n        .form(&params)\n        .send()\n        .context(\"OAuth device-code request failed\")?;\n    let (status, body): (_, DeviceGrantResponse) =\n        parse_oauth_json(response, \"OAuth device-code request\")?;\n    if !status.is_success() || body.error.is_some() {\n        let detail = oauth_failure_detail(\n            body.error.as_deref(),\n            body.error_description.as_deref(),\n            status,\n        );\n        bail!(\"OAuth device-code request failed ({detail})\");\n    }\n    if body\n        .device_code\n        .as_deref()\n        .is_some_and(|code| !code.trim().is_empty())\n        && body\n            .user_code\n            .as_deref()\n            .is_some_and(|code| !code.trim().is_empty())\n    {\n        return Ok(body);\n    }\n    bail!(\"OAuth device-code request returned success without a device and user code\");\n}\n\n/// Poll the token endpoint once, classifying the RFC 8628 outcome. Matches\n/// the legacy per-provider poll so the ported tests pin identical behavior.\nfn poll_device_grant(","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L723-L759","documentation":"Thrown when the OAuth 2.0 Device Authorization Grant request (RFC 8628) returns a non-success HTTP status or an OAuth error payload. The helper `oauth_failure_detail` builds a human-readable detail from the provider's `error`/`error_description` fields, falling back to the HTTP status. This library throws it so callers get a single classified message instead of raw HTTP output.","triggerScenarios":"Calling `request_device_grant` (via `device_code_login`) when the device-authorization endpoint returns status >= 400 or a body containing an `error` field, e.g. `invalid_client`, `slow_down`, or an expired/incorrect client_id.","commonSituations":"Wrong or stale `client_id` configured for the provider; issuer/discovery resolving to the wrong tenant; provider requiring scopes the client is not allowed; network proxy returning 4xx/5xx; provider outage returning 500 with an error body.","solutions":["Check the provider's client_id and scopes configured for this OAuth provider entry","Use the `detail` in the message: match the provider's `error` code (e.g. invalid_client means wrong client credentials)","Re-run `codewhale` provider discovery or fix the issuer URL so the correct device-authorization endpoint is resolved","Retry later if the status is 5xx (provider-side outage)"],"exampleFix":"// before (generic failure)\nlet grant = device_code_login(provider)?;\n// after (inspect detail first, fix credentials)\nmatch device_code_login(provider) {\n    Ok(grant) => grant,\n    Err(e) if e.to_string().contains(\"invalid_client\") => {\n        eprintln!(\"fix client_id for this provider\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"// ensure client_id and scopes are set before requesting the grant\nif client_id.trim().is_empty() || scopes.trim().is_empty() {\n    return Err(\"client_id and scopes must be configured for device login\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    let grant = device_code_login(provider).await?;\n} catch (e) {\n    // message contains provider failure detail (error code or HTTP status)\n    logError(\"device grant failed\", e);\n    if (isRetryableStatus(e)) scheduleRetry(); else showReLoginHint(provider);\n}","preventionTips":["Keep provider client_id/scopes configuration validated at startup","Resolve endpoints from fresh discovery metadata before each login","Monitor provider status pages; surface 5xx as transient to users"],"tags":["oauth","network","http"],"backgroundTag":"oauth-token-exchange-failed","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"}