{"record":{"id":"0c258d3d5296bdfc","repo":"sigoden/aichat","slug":"invalid-access-token","errorCode":null,"errorMessage":"Invalid access token","messagePattern":"Invalid access token","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/client/access_token.rs","lineNumber":15,"sourceCode":"use anyhow::{anyhow, Result};\nuse chrono::Utc;\nuse indexmap::IndexMap;\nuse parking_lot::RwLock;\nuse std::sync::LazyLock;\n\nstatic ACCESS_TOKENS: LazyLock<RwLock<IndexMap<String, (String, i64)>>> =\n    LazyLock::new(|| RwLock::new(IndexMap::new()));\n\npub fn get_access_token(client_name: &str) -> Result<String> {\n    ACCESS_TOKENS\n        .read()\n        .get(client_name)\n        .map(|(token, _)| token.clone())\n        .ok_or_else(|| anyhow!(\"Invalid access token\"))\n}\n\npub fn is_valid_access_token(client_name: &str) -> bool {\n    let access_tokens = ACCESS_TOKENS.read();\n    let (token, expires_at) = match access_tokens.get(client_name) {\n        Some(v) => v,\n        None => return false,\n    };\n    !token.is_empty() && Utc::now().timestamp() < *expires_at\n}\n\npub fn set_access_token(client_name: &str, token: String, expires_at: i64) {\n    let mut access_tokens = ACCESS_TOKENS.write();\n    let entry = access_tokens.entry(client_name.to_string()).or_default();\n    entry.0 = token;\n    entry.1 = expires_at;\n}\n","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/client/access_token.rs#L1-L33","documentation":"get_access_token looks up a stored access token for the given client name in the ACCESS_TOKENS map; if no entry exists (never authenticated, expired token removed, or wrong client name), it returns 'Invalid access token'. Callers like prepare_chat_completions/prepare_embeddings need this token to authorize API requests.","triggerScenarios":"Calling get_access_token (indirectly via prepare_chat_completions/prepare_embeddings) for a client_name that has no entry in ACCESS_TOKENS — token never obtained, was cleared after expiry, or the name is misspelled.","commonSituations":"Auth/login step was skipped before making chat/embedding calls; access token expired and was not refreshed; client name mismatch between token storage and lookup; using a client that requires OAuth without completing the token exchange.","solutions":["Run the authentication/login flow first so a token is stored for this client","Check that the client_name used for lookup matches the one used when storing the token","Refresh or re-obtain the access token if it expired and was evicted","Verify the provider actually requires/uses access-token auth and your config points at the right client"],"exampleFix":"// before\nlet token = get_access_token(\"claude\")?; // never authenticated\n// after\nensure_access_token(\"claude\")?; // login/refresh first\nlet token = get_access_token(\"claude\")?;","handlingStrategy":"validation","validationCode":"// guard before calling APIs that need the token\nif !is_valid_access_token(\"claude\") {\n    // run login/token-refresh flow first\n    refresh_access_token(\"claude\")?;\n}\nlet token = get_access_token(\"claude\")?;","typeGuard":"fn token_available(client_name: &str) -> bool { is_valid_access_token(client_name) }","tryCatchPattern":"let token = match get_access_token(client_name) {\n    Ok(t) => t,\n    Err(_) => { perform_login(client_name)?; get_access_token(client_name)? }\n};","preventionTips":["Always complete the auth/login flow before chat/embedding calls","Refresh tokens proactively based on the stored expires_at timestamp","Use a single constant for each client name to avoid lookup mismatches","Check is_valid_access_token before operations requiring auth"],"tags":["auth","token","oauth"],"backgroundTag":"authentication-required","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}