Hmbown/CodeWhale · error

credential file must be a JSON object of entries

Error message

credential file {} must be a JSON object of entries

What it means

After JSON parsing succeeds, parse_auth_file requires the top-level value to be a JSON object whose keys are credential entry names. Arrays, strings, numbers, or null at the top level produce this error. The schema is a map of named entries, not a bare credential.

Solutions

  1. Wrap the credential in a named entry: {"<entry-name>": { ...credential... }}.
  2. Delete the file and re-run the provider login to regenerate the correct shape.
  3. If migrating from another tool, use the documented import path (e.g. `codewhale auth xai-device` or the Grok CLI import) instead of hand-copying.

Example fix

// before
{ "access_token": "...", "refresh_token": "..." }
// after
{ "default": { "access_token": "...", "refresh_token": "..." } }
Defensive patterns

Strategy: validation

Validate before calling

const v = JSON.parse(fs.readFileSync(p, 'utf8')); if (v === null || typeof v !== 'object' || Array.isArray(v)) restructure(p);

Type guard

const isEntryMap = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);

Try / catch

try { useCredentials(); } catch (e) { if (String(e).includes('must be a JSON object of entries')) { await relogin(); } }

Prevention

When it happens

Trigger: parse_auth_file receives valid JSON whose root is not an object — e.g. a file containing a single credential object instead of {"name": {...}}, or an array of entries.

Common situations: User pastes one provider's credential JSON (from another tool's export) directly into the store instead of nesting it under an entry name; a migration wrote the wrong shape.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

fn load_owned_auth_file_from_store(
    store: &codewhale_config::XaiOAuthCredentialStore,
    name: &str,
) -> Result<Option<AuthFile>> {
    let Some(raw) = store.read_to_string(name)? else {
        return Ok(None);
    };
    parse_auth_file(&raw, &store.path_for(name)?).map(Some)
}

fn parse_auth_file(raw: &str, path: &Path) -> Result<AuthFile> {
    let value: Value = serde_json::from_str(raw).map_err(|_| {
        anyhow::anyhow!(
            "credential file {} is not valid credential JSON",
            codewhale_config::quote_os_path(path)
        )
    })?;
    let obj = value.as_object().ok_or_else(|| {
        anyhow::anyhow!(
            "credential file {} must be a JSON object of entries",
            codewhale_config::quote_os_path(path)
        )
    })?;
    let mut out = BTreeMap::new();
    for (k, v) in obj {
        match serde_json::from_value::<OwnedAuthEntry>(v.clone()) {
            Ok(entry) => {
                out.insert(k.clone(), entry);
            }
            Err(_) => {
                tracing::warn!(
                    target: "codewhale::oauth",
                    "skipping unreadable owned auth entry"
                );
            }
        }
    }

View on GitHub (pinned to 73e0f67d83)