BigPizzaV3/CodexPlusPlus · error · anyhow::Error

auth.json 必须是 JSON 对象

Error message

auth.json 必须是 JSON 对象

What it means

set_openai_api_key_in_auth_contents 负责把 API Key 写入 auth.json JSON(或空 key 时移除 OPENAI_API_KEY 键)。当前实现里非对象 JSON 会在进入分支前被重置为 json!({})(2113-2115 行),因此末尾的 bail「auth.json 必须是 JSON 对象」在当前版本中是防御性死代码,正常不可达;它只在旧版本二进制或该段逻辑被改动后才会出现。JSON 解析失败走的是另一条消息「auth.json JSON 解析失败」。

Source

Thrown at crates/codex-plus-core/src/relay_config.rs:2126

    let mut auth = if auth_contents.trim().is_empty() {
        json!({})
    } else {
        serde_json::from_str::<Value>(auth_contents).with_context(|| "auth.json JSON 解析失败")?
    };
    if !auth.is_object() {
        auth = json!({});
    }
    if let Some(auth_object) = auth.as_object_mut() {
        if api_key.trim().is_empty() {
            auth_object.remove("OPENAI_API_KEY");
        } else {
            auth_object.insert(
                "OPENAI_API_KEY".to_string(),
                Value::String(api_key.trim().to_string()),
            );
        }
    } else {
        anyhow::bail!("auth.json 必须是 JSON 对象");
    }
    Ok(serde_json::to_string_pretty(&auth)?)
}

fn set_experimental_bearer_token_in_config(
    config_contents: &str,
    api_key: &str,
) -> anyhow::Result<String> {
    let mut doc = parse_toml_document(config_contents)?;
    let provider_id = active_or_default_provider_id(&doc);
    let provider = ensure_provider_table(&mut doc, &provider_id)?;
    if api_key.trim().is_empty() {
        provider.remove("experimental_bearer_token");
    } else {
        provider["experimental_bearer_token"] = toml_edit::value(api_key.trim());
    }
    Ok(move_model_providers_before_profiles(
        &ensure_trailing_newline(doc.to_string()),

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. 把 auth.json 修为 JSON 对象(如 {} 或 {"OPENAI_API_KEY": "sk-..."})
  2. 升级到当前版本(非对象会被自动重置为 {} 而不再报错)
  3. 写入前用 serde_json 校验根节点 is_object()

Example fix

// before (auth.json)
["sk-abc"]

// after (auth.json)
{
  "OPENAI_API_KEY": "sk-abc"
}
Defensive patterns

Strategy: validation

Validate before calling

fn is_json_object_or_empty(text: &str) -> bool {
    let t = text.trim();
    t.is_empty()
        || serde_json::from_str::<serde_json::Value>(t)
            .map(|v| v.is_object())
            .unwrap_or(false)
}
anyhow::ensure!(is_json_object_or_empty(&auth_contents), "auth.json 必须是 JSON 对象");

Type guard

fn auth_contents_is_object(text: &str) -> bool {
    serde_json::from_str::<serde_json::Value>(text)
        .map(|v| v.is_object())
        .unwrap_or(false)
}

Try / catch

if let Err(err) = apply_relay_profile_to_home_with_switch_rules_and_computer_use_guard(&home, &profile, &common, guard) {
    if err.to_string().contains("auth.json 必须是 JSON 对象") {
        // 当前版本该分支不可达;若出现说明运行的是旧二进制或代码被改动,先升级再排查
    }
}

Prevention

When it happens

Trigger: auth_contents 是合法 JSON 但根节点不是对象(数组/字符串/数字),且运行的是未做非对象重置的旧版本;当前代码路径下此 bail 不可达(非对象已被替换为 {})。

Common situations: 用户手工把 auth.json 改成数组或字符串;使用旧版 CodexPlusPlus 二进制;其他工具写入非对象 auth.json。

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/53ee88a86cda188b. Report an issue: GitHub.