{"record":{"id":"52bc5f80dfe52e66","repo":"zeroclaw-labs/zeroclaw","slug":"email-oauth2-profile-is-not-oauth-based-profile","errorCode":null,"errorMessage":"Email OAuth2 profile is not OAuth-based: {profile_id}","messagePattern":"Email OAuth2 profile is not OAuth-based: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/mod.rs","lineNumber":516,"sourceCode":"        channel_alias: &str,\n        profile_override: Option<&str>,\n        token_url: &str,\n        client_id: &str,\n        scopes: &[String],\n    ) -> Result<Option<String>> {\n        const SKEW_SECS: u64 = 90;\n\n        let data = self.store.load().await?;\n        let Some(profile_id) = select_profile_id(&data, channel_alias, profile_override) else {\n            return Ok(None);\n        };\n\n        let Some(profile) = data.profiles.get(&profile_id) else {\n            return Ok(None);\n        };\n\n        let Some(token_set) = profile.token_set.as_ref() else {\n            anyhow::bail!(\"Email OAuth2 profile is not OAuth-based: {profile_id}\");\n        };\n\n        if !token_set.is_expiring_within(Duration::from_secs(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            // No refresh token; return the (possibly expired) access token and\n            // let the IMAP auth failure surface as a log event.\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 acquiring lock to avoid duplicate refreshes.\n        let data = self.store.load().await?;\n        let Some(latest_profile) = data.profiles.get(&profile_id) else {","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/mod.rs#L498-L534","documentation":"get_valid_email_oauth2_token selected a profile for the email channel alias (e.g. \"email.hotmail\") but its token_set is None: the stored profile is not OAuth-based. Email channels require an OAuth2 token set (access + refresh token) so the resolver can refresh before IMAP connects; a bearer-token or malformed profile is a hard error.","triggerScenarios":"Calling imap_connect or get_oauth2_token for an email channel whose profile was never completed through the email OAuth2 setup flow, or whose profiles entry lost the token_set field (partial setup, manual edit, wrong channel alias key).","commonSituations":"Email channel configured in config.toml but the OAuth grant was never finished; channel alias renamed so a new empty profile is selected; profiles file migrated between machines without the token set.","solutions":["Re-run the email channel OAuth2 setup for that channel alias to store a full token set","Confirm the channel alias used in config matches the alias the profile was stored under (profiles are keyed by the alias)","Inspect the stored profile and delete the broken entry so setup can recreate it cleanly"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let data = auth.load_profiles().await?;\nlet profile_id = data.profiles.keys().find(|k| k.starts_with(&format!(\"{channel_alias}:\")));\nif let Some(id) = profile_id {\n    anyhow::ensure!(\n        data.profiles[id].token_set.is_some(),\n        \"email profile {id} has no OAuth token set; re-run the channel OAuth setup\"\n    );\n}\nlet token = auth.get_valid_email_oauth2_token(channel_alias, None, url, id, scopes).await?;","typeGuard":"fn is_oauth_profile(p: &AuthProfile) -> bool {\n    p.token_set.is_some()\n}","tryCatchPattern":"match auth.get_valid_email_oauth2_token(alias, None, url, id, scopes).await {\n    Ok(tok) => tok,\n    Err(e) if e.to_string().contains(\"not OAuth-based\") => {\n        eprintln!(\"channel '{alias}' needs a completed OAuth grant; re-run setup\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Finish the email channel OAuth grant before enabling IMAP for the channel","Keep the channel alias identical in config and in the stored profile key","Delete broken profile entries so setup recreates them with a full token set"],"tags":["auth","oauth","email","imap","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"}