Hmbown/CodeWhale · error

Codex credential file

Error message

Codex credential file {} is not valid credential JSON

What it means

`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.

Solutions

  1. Delete the corrupt credential file and re-run the Codex OAuth sign-in flow to regenerate it
  2. Validate the file is parseable JSON with the expected top-level fields before relying on it
  3. Check for interrupted writes/backups (e.g. a `.tmp` file) and restore a complete copy

Example fix

// before: manually patched tokens.json by hand, left trailing comma
{"tokens": {"access": "...",}}
// after: re-authenticate to let the library write valid JSON
rm ~/.codex/auth.json   # then run the sign-in flow
{"tokens": {"access": "...", "refresh": "..."}}
Defensive patterns

Strategy: try-catch

Validate before calling

fn credentials_look_valid(path: &Path) -> bool {
    std::fs::read_to_string(path)
        .ok()
        .and_then(|c| serde_json::from_str::<serde_json::Value>(&c).ok())
        .map_or(false, |v| v.get("tokens").is_some())
}

Try / catch

match oauth::get_credentials() {
    Ok(creds) => creds,
    Err(e) if e.to_string().contains("not valid credential JSON") => {
        delete_credential_file();
        start_fresh_sign_in()?  // regenerate credentials
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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`).

Common situations: 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.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/e39fdf19a72860f2. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:120

            // 60-second safety margin
            now + 60 >= exp
        }
        // If we can't prove freshness, fail closed. External credentials are
        // never refreshed by Codewhale.
        None => true,
    }
}

/// Load Codex credentials from the auth file.
///
/// Returns `Ok(None)` if the file doesn't exist or has no usable tokens.
/// Returns `Err` only on parse/IO errors that aren't "file not found".
fn load_credentials(grant: &ExternalCredentialReadGrant) -> Result<Option<CodexCredentials>> {
    let Some(contents) = crate::external_credentials::read_to_string(grant)? else {
        return Ok(None);
    };
    let auth: CodexAuthFile = serde_json::from_str(&contents).map_err(|_| {
        anyhow::anyhow!(
            "Codex credential file {} is not valid credential JSON",
            codewhale_config::quote_os_path(grant.path())
        )
    })?;
    let tokens = match auth.tokens {
        Some(t) => t,
        None => return Ok(None),
    };
    let access_token = match tokens.access_token {
        Some(t) if !t.trim().is_empty() => t,
        _ => return Ok(None),
    };
    Ok(Some(CodexCredentials {
        access_token,
        account_id: tokens.account_id,
    }))
}

View on GitHub (pinned to 73e0f67d83)