{"record":{"id":"5bfd5bab11027eb1","repo":"Hmbown/CodeWhale","slug":"name-oauth-operation-failed-err","errorCode":null,"errorMessage":"{name} OAuth {operation} failed ({err})","messagePattern":"(.+?) OAuth (.+?) failed \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":991,"sourceCode":"    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    }\n    anyhow::ensure!(\n        parsed\n            .access_token\n            .as_deref()\n            .is_some_and(|token| !token.trim().is_empty()),\n        \"{name} OAuth {operation} returned an empty access token\"\n    );\n    Ok(parsed)\n}\n\nfn compact_form_error(body: &str) -> String {\n    body.chars().filter(|c| !c.is_control()).take(80).collect()\n}\n\n/// Refresh an owned token through the seam at an explicit token URL —\n/// discovered when the provider row demands it, pinned otherwise. Refresh is\n/// a Codewhale-owned credential operation only: external imports never","sourceCodeStart":973,"sourceCodeEnd":1009,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L973-L1009","documentation":"The generic (non-permanent) counterpart to the permanent-refresh error: thrown when an OAuth operation fails with a transient or otherwise unclassified error — status not 401 and error code not in the permanent set. It surfaces the provider name, operation, and underlying error without a re-login instruction, so callers may retry.","triggerScenarios":"Token refresh/exchange receiving a 5xx, 429, network timeout, or an OAuth error code not classified as permanent (e.g. `temporarily_unavailable`, `server_error`).","commonSituations":"Provider outage or rate limiting during refresh; transient network failure; brief provider-side hiccups during token exchange.","solutions":["Retry after a short backoff — this failure class is transient by classification","Back off further on 429 (rate limit) before retrying","If retries persistently fail, check provider status page, then sign in again via the relogin hint","Log the `err` detail to identify any misclassified permanent error"],"exampleFix":"// before\nlet token = refresh_access_token(provider, refresh)?; // no retry on transient failure\n// after\nlet token = match refresh_access_token(provider, refresh) {\n    Ok(t) => t,\n    Err(e) if e.to_string().contains(\"failed permanently\") => return Err(e),\n    Err(_) => {\n        tokio::time::sleep(Duration::from_secs(5)).await;\n        refresh_access_token(provider, refresh)?\n    }\n};","handlingStrategy":"retry","validationCode":"// pre-check network reachability to reduce transient failures\nawait fetch(tokenEndpoint, { method: 'HEAD' }).catch(() => warn('token endpoint unreachable'));","typeGuard":null,"tryCatchPattern":"try {\n  await refreshAccessToken(provider);\n} catch (e) {\n  if (String(e).includes('failed permanently')) throw e;\n  await sleep(backoff); // transient: retry with backoff\n  return refreshAccessToken(provider);\n}","preventionTips":["Apply exponential backoff with jitter on transient OAuth failures","Cap retry attempts and then fall back to interactive re-login","Log the `err` detail to distinguish rate limits from outages"],"tags":["oauth","token-refresh","retryable","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-23T08:17:48.524Z"}