{"record":{"id":"e39fdf19a72860f2","repo":"Hmbown/CodeWhale","slug":"codex-credential-file-is-not-valid-credential-j","errorCode":null,"errorMessage":"Codex credential file {} is not valid credential JSON","messagePattern":"Codex credential file (.+?) is not valid credential JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":120,"sourceCode":"            // 60-second safety margin\n            now + 60 >= exp\n        }\n        // If we can't prove freshness, fail closed. External credentials are\n        // never refreshed by Codewhale.\n        None => true,\n    }\n}\n\n/// Load Codex credentials from the auth file.\n///\n/// Returns `Ok(None)` if the file doesn't exist or has no usable tokens.\n/// Returns `Err` only on parse/IO errors that aren't \"file not found\".\nfn load_credentials(grant: &ExternalCredentialReadGrant) -> Result<Option<CodexCredentials>> {\n    let Some(contents) = crate::external_credentials::read_to_string(grant)? else {\n        return Ok(None);\n    };\n    let auth: CodexAuthFile = serde_json::from_str(&contents).map_err(|_| {\n        anyhow::anyhow!(\n            \"Codex credential file {} is not valid credential JSON\",\n            codewhale_config::quote_os_path(grant.path())\n        )\n    })?;\n    let tokens = match auth.tokens {\n        Some(t) => t,\n        None => return Ok(None),\n    };\n    let access_token = match tokens.access_token {\n        Some(t) if !t.trim().is_empty() => t,\n        _ => return Ok(None),\n    };\n    Ok(Some(CodexCredentials {\n        access_token,\n        account_id: tokens.account_id,\n    }))\n}\n","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L102-L138","documentation":"`oauth::load_credentials` reads the stored Codex OAuth credential file and parses it as `CodexAuthFile` JSON. If the file exists but is not valid JSON (or not the expected credential shape), the parse error is converted into this message. The path is inserted via `quote_os_path` specifically so that malformed-file errors never echo raw file contents, which could leak tokens.","triggerScenarios":"Calling `get_credentials`/`stored_credentials_present` when the credential file at the granted path contains non-JSON data, an empty/truncated file, or JSON that does not deserialize into `CodexAuthFile` (e.g. missing `tokens`).","commonSituations":"A manual edit or partial write of the credential file left it corrupt; a credential file created by an older/other tool with a different schema; disk-full or crash during a previous token write left truncated JSON; the file was overwritten by unrelated content.","solutions":["Delete the corrupt credential file and re-run the Codex OAuth sign-in flow to regenerate it","Validate the file is parseable JSON with the expected top-level fields before relying on it","Check for interrupted writes/backups (e.g. a `.tmp` file) and restore a complete copy"],"exampleFix":"// before: manually patched tokens.json by hand, left trailing comma\n{\"tokens\": {\"access\": \"...\",}}\n// after: re-authenticate to let the library write valid JSON\nrm ~/.codex/auth.json   # then run the sign-in flow\n{\"tokens\": {\"access\": \"...\", \"refresh\": \"...\"}}","handlingStrategy":"try-catch","validationCode":"fn credentials_look_valid(path: &Path) -> bool {\n    std::fs::read_to_string(path)\n        .ok()\n        .and_then(|c| serde_json::from_str::<serde_json::Value>(&c).ok())\n        .map_or(false, |v| v.get(\"tokens\").is_some())\n}","typeGuard":null,"tryCatchPattern":"match oauth::get_credentials() {\n    Ok(creds) => creds,\n    Err(e) if e.to_string().contains(\"not valid credential JSON\") => {\n        delete_credential_file();\n        start_fresh_sign_in()?  // regenerate credentials\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never hand-edit credential files; always re-run the OAuth sign-in to change them","Ensure token writes are atomic (write temp + rename) to avoid truncated files","Check disk space / write completion before assuming credentials saved","Treat 'stored_credentials_present' with the same parse check, not just file existence"],"tags":["oauth","credentials","json","file-corrupt"],"backgroundTag":"json-parse-error","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}