{"record":{"id":"93d7b9951b1e6a0b","repo":"zeroclaw-labs/zeroclaw","slug":"gemini-auth-profile-is-missing-token-set-profile","errorCode":null,"errorMessage":"Gemini auth profile is missing token set: {profile_id}","messagePattern":"Gemini auth profile is missing token set: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/mod.rs","lineNumber":324,"sourceCode":"        if !token_set.is_expiring_within(Duration::from_secs(OPENAI_REFRESH_SKEW_SECS)) {\n            return Ok(Some(token_set.access_token.clone()));\n        }\n\n        let Some(refresh_token) = token_set.refresh_token.clone() else {\n            return Ok(Some(token_set.access_token.clone()));\n        };\n\n        let refresh_lock = refresh_lock_for_profile(&profile_id);\n        let _guard = refresh_lock.lock().await;\n\n        // Re-load after waiting for lock to avoid duplicate refreshes.\n        let data = self.store.load().await?;\n        let Some(latest_profile) = data.profiles.get(&profile_id) else {\n            return Ok(None);\n        };\n\n        let Some(latest_tokens) = latest_profile.token_set.as_ref() else {\n            anyhow::bail!(\"Gemini auth profile is missing token set: {profile_id}\");\n        };\n\n        if !latest_tokens.is_expiring_within(Duration::from_secs(OPENAI_REFRESH_SKEW_SECS)) {\n            return Ok(Some(latest_tokens.access_token.clone()));\n        }\n\n        let refresh_token = latest_tokens.refresh_token.clone().unwrap_or(refresh_token);\n\n        if let Some(remaining) = refresh_backoff_remaining(&profile_id) {\n            anyhow::bail!(\n                \"Gemini token refresh is in backoff for {remaining}s due to previous failures\"\n            );\n        }\n\n        let mut refreshed = match refresh_gemini_access_token_with_retries(\n            &self.client,\n            client_id,\n            client_secret,","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/mod.rs#L306-L342","documentation":"Post-lock twin of error 673 in get_valid_gemini_access_token: after taking the per-profile refresh mutex and re-loading the store, the profile's token_set is now None. The profile changed between the two loads — a concurrent write replaced the OAuth token set with a bearer token or dropped it.","triggerScenarios":"A concurrent auth paste-token / profile overwrite lands between the first store load and the post-lock re-load inside get_valid_gemini_access_token, while send_generate_content or warmup was resolving credentials.","commonSituations":"CLI auth commands and a running gateway share one profiles file; two instances point at the same state dir; external tooling rewrites the profiles JSON.","solutions":["Retry the credential resolution once the concurrent profile write completes","Keep one writer for the auth profiles file (single instance or single state dir)","Do not flip a gemini profile between OAuth and token kinds while requests are in flight"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Single-writer profiles file avoids token_set disappearing between loads.\nlet data = auth.load_profiles().await?;\nanyhow::ensure!(\n    data.profiles.get(&profile_id).map(|p| p.token_set.is_some()).unwrap_or(false),\n    \"gemini profile lost its token set mid-flight\"\n);","typeGuard":"fn is_oauth_profile(p: &AuthProfile) -> bool {\n    p.token_set.is_some()\n}","tryCatchPattern":"match auth.get_valid_gemini_access_token(override_, cid, secret).await {\n    Ok(tok) => tok,\n    Err(e) if e.to_string().contains(\"missing token set\") => {\n        tokio::time::sleep(std::time::Duration::from_millis(500)).await;\n        auth.get_valid_gemini_access_token(override_, cid, secret).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["One state dir per process to avoid concurrent profile rewrites","Retry once after a brief pause when this lands during parallel auth commands","Never switch a gemini profile between token kinds while requests run"],"tags":["auth","oauth","gemini","race-condition","profile","rust"],"backgroundTag":"auth-profile-not-oauth","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}