{"record":{"id":"9835fbd863c45fae","repo":"Hmbown/CodeWhale","slug":"agy-oauth-token-json-carries-no-access-token-membe","errorCode":null,"errorMessage":"agy OAuth token JSON carries no access token member","messagePattern":"agy OAuth token JSON carries no access token member","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/agy_credentials.rs","lineNumber":224,"sourceCode":"    let Some(raw) = value else {\n        return Ok(None);\n    };\n    let trimmed = raw.trim();\n    if trimmed.is_empty() {\n        return Ok(None);\n    }\n    if trimmed.starts_with('{') {\n        let parsed: serde_json::Value = serde_json::from_str(trimmed)\n            .with_context(|| \"agy OAuth token value is malformed JSON\")?;\n        for member in [\"access_token\", \"accessToken\", \"token\"] {\n            if let Some(token) = parsed.get(member).and_then(|v| v.as_str()) {\n                if token.trim().is_empty() {\n                    bail!(\"agy OAuth token member `{member}` is empty\");\n                }\n                return Ok(Some(token.to_string()));\n            }\n        }\n        bail!(\"agy OAuth token JSON carries no access token member\");\n    }\n    Ok(Some(trimmed.to_string()))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use codewhale_config::ExternalCredentialReadGrant;\n    use std::collections::HashMap;\n\n    fn grant_for(path: &Path) -> ExternalCredentialReadGrant {\n        codewhale_config::ExternalCredentialConsentToml::read_only(\n            codewhale_config::ProviderKind::Antigravity,\n            ExternalCredentialSource::AgyCli,\n            path.to_path_buf(),\n        )\n        .read_grant(\n            codewhale_config::ProviderKind::Antigravity,","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/agy_credentials.rs#L206-L242","documentation":"The raw agy token value parsed as valid JSON, but none of the accepted members (`access_token`, `accessToken`, `token`) is present as a string. The credential row holds structured data that is not an OAuth token payload, so the importer refuses to guess.","triggerScenarios":"parse_agy_oauth_token_value receives JSON such as `{\"version\":1,\"settings\":{...}}` or a token wrapped under a different key (e.g. `{\"auth\": {...}}`) — the row exists but is not a token record.","commonSituations":"The agy client changed its schema and stores the token under a new key or table; the grant reads a settings-oriented row instead of the auth row; a future/older client version with a different payload shape.","solutions":["Confirm the agy client is signed in, so the auth row with `access_token` (or `accessToken`/`token`) actually exists, then retry.","Inspect the stored value (read-only) to see which member names it carries and align the importer with the supported set.","Update to matching codewhale/agy versions if the payload schema changed."],"exampleFix":"// before\nlet token = antigravity_oauth_token_from_grant(&grant)?; // bails: no access token member\n\n// after\n// verify the payload shape before import\nlet raw = read_token_row_readonly(grant.path())?; // your read helper\nlet v: serde_json::Value = serde_json::from_str(&raw)?;\nif v.get(\"access_token\").or_else(|| v.get(\"accessToken\")).and_then(|t| t.as_str()).is_none() {\n    anyhow::bail!(\"agy client is signed out or uses an unsupported token schema\");\n}\nlet token = antigravity_oauth_token_from_grant(&grant)?;","handlingStrategy":"try-catch","validationCode":"let raw = peek_token_value(grant.path())?;\nif let Some(v) = raw.as_deref().filter(|v| v.trim().starts_with('{')) {\n    let json: serde_json::Value = serde_json::from_str(v.trim())?;\n    if ![\"access_token\", \"accessToken\", \"token\"].iter().any(|m| json.get(*m).is_some()) {\n        anyhow::bail!(\"stored payload has no token member; sign in to agy first\");\n    }\n}","typeGuard":"fn carries_token_member(v: &serde_json::Value) -> bool {\n    v.get(\"access_token\").or_else(|| v.get(\"accessToken\")).or_else(|| v.get(\"token\")).is_some()\n}","tryCatchPattern":"match antigravity_oauth_token_from_grant(&grant) {\n    Ok(t) => t,\n    Err(e) if e.to_string().contains(\"no access token member\") => { /* schema mismatch or signed out */ return Err(e.context(\"update codewhale or sign in to agy\")) }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep codewhale and the agy client versions in lockstep so the token payload schema matches.","Confirm the client is signed in — the auth row only exists after login.","When introspecting the store read-only, print member names only; never log token values."],"tags":["antigravity","oauth","json","credentials"],"backgroundTag":"oauth-token-not-found","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}