{"record":{"id":"736030047771ae8c","repo":"zeroclaw-labs/zeroclaw","slug":"openai-codex-auth-profile-is-missing-token-set-p","errorCode":null,"errorMessage":"OpenAI Codex auth profile is missing token set: {profile_id}","messagePattern":"OpenAI Codex auth profile is missing token set: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/mod.rs","lineNumber":236,"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!(\"OpenAI Codex 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                \"OpenAI token refresh is in backoff for {remaining}s due to previous failures\"\n            );\n        }\n\n        let mut refreshed =\n            match refresh_openai_access_token_with_retries(&self.client, &refresh_token).await {\n                Ok(tokens) => {\n                    clear_refresh_backoff(&profile_id);","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/mod.rs#L218-L254","documentation":"The second-half twin of error 670 inside get_valid_openai_access_token: after acquiring the per-profile refresh lock, the function re-loads the store and requires the profile to still have a token_set. Between the first load and this re-load the profile lost its token_set — typically because a concurrent paste-token or profile edit rewrote it to token kind while a refresh was in flight.","triggerScenarios":"Two tasks call get_valid_openai_access_token around the same time the same profile is overwritten by store_model_provider_token (bearer token), or the profiles file is edited externally between the two loads; the first load saw a token_set, the post-lock load does not.","commonSituations":"A CLI auth paste-token runs while a long-lived gateway process refreshes tokens; scripts rewriting the auth profiles JSON concurrently; profiles file shared between two zeroclaw instances.","solutions":["Re-run the credential resolution after the concurrent write finishes — the profile state seen on the next load is consistent","Avoid mixing profile kinds: keep an openai-codex profile either OAuth (auth login) or bearer (paste-token), not both written by racing processes","Give each zeroclaw instance its own state dir so profile files are not shared"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Serialize profile writes: a single writer prevents token_set from vanishing mid-refresh.\n// Before resolving credentials, confirm the profile still holds a token set.\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    \"profile lost its token set; wait for concurrent auth writes to finish\"\n);","typeGuard":"fn is_oauth_profile(p: &AuthProfile) -> bool {\n    p.token_set.is_some()\n}","tryCatchPattern":"match auth.get_valid_openai_access_token(override_).await {\n    Ok(tok) => tok,\n    Err(e) if e.to_string().contains(\"missing token set\") => {\n        // profile was rewritten between loads; reload and retry once\n        tokio::time::sleep(std::time::Duration::from_millis(500)).await;\n        auth.get_valid_openai_access_token(override_).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Give each process its own state dir so profile files have one writer","Do not run paste-token while a gateway is refreshing the same profile","Retry once after a short delay when the error appears during concurrent auth activity"],"tags":["auth","oauth","openai-codex","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"}