{"record":{"id":"53ee88a86cda188b","repo":"BigPizzaV3/CodexPlusPlus","slug":"auth-json-json","errorCode":null,"errorMessage":"auth.json 必须是 JSON 对象","messagePattern":"auth\\.json 必须是 JSON 对象","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":2126,"sourceCode":"    let mut auth = if auth_contents.trim().is_empty() {\n        json!({})\n    } else {\n        serde_json::from_str::<Value>(auth_contents).with_context(|| \"auth.json JSON 解析失败\")?\n    };\n    if !auth.is_object() {\n        auth = json!({});\n    }\n    if let Some(auth_object) = auth.as_object_mut() {\n        if api_key.trim().is_empty() {\n            auth_object.remove(\"OPENAI_API_KEY\");\n        } else {\n            auth_object.insert(\n                \"OPENAI_API_KEY\".to_string(),\n                Value::String(api_key.trim().to_string()),\n            );\n        }\n    } else {\n        anyhow::bail!(\"auth.json 必须是 JSON 对象\");\n    }\n    Ok(serde_json::to_string_pretty(&auth)?)\n}\n\nfn set_experimental_bearer_token_in_config(\n    config_contents: &str,\n    api_key: &str,\n) -> anyhow::Result<String> {\n    let mut doc = parse_toml_document(config_contents)?;\n    let provider_id = active_or_default_provider_id(&doc);\n    let provider = ensure_provider_table(&mut doc, &provider_id)?;\n    if api_key.trim().is_empty() {\n        provider.remove(\"experimental_bearer_token\");\n    } else {\n        provider[\"experimental_bearer_token\"] = toml_edit::value(api_key.trim());\n    }\n    Ok(move_model_providers_before_profiles(\n        &ensure_trailing_newline(doc.to_string()),","sourceCodeStart":2108,"sourceCodeEnd":2144,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_config.rs#L2108-L2144","documentation":"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 解析失败」。","triggerScenarios":"auth_contents 是合法 JSON 但根节点不是对象（数组/字符串/数字），且运行的是未做非对象重置的旧版本；当前代码路径下此 bail 不可达（非对象已被替换为 {}）。","commonSituations":"用户手工把 auth.json 改成数组或字符串；使用旧版 CodexPlusPlus 二进制；其他工具写入非对象 auth.json。","solutions":["把 auth.json 修为 JSON 对象（如 {} 或 {\"OPENAI_API_KEY\": \"sk-...\"}）","升级到当前版本（非对象会被自动重置为 {} 而不再报错）","写入前用 serde_json 校验根节点 is_object()"],"exampleFix":"// before (auth.json)\n[\"sk-abc\"]\n\n// after (auth.json)\n{\n  \"OPENAI_API_KEY\": \"sk-abc\"\n}","handlingStrategy":"validation","validationCode":"fn is_json_object_or_empty(text: &str) -> bool {\n    let t = text.trim();\n    t.is_empty()\n        || serde_json::from_str::<serde_json::Value>(t)\n            .map(|v| v.is_object())\n            .unwrap_or(false)\n}\nanyhow::ensure!(is_json_object_or_empty(&auth_contents), \"auth.json 必须是 JSON 对象\");","typeGuard":"fn auth_contents_is_object(text: &str) -> bool {\n    serde_json::from_str::<serde_json::Value>(text)\n        .map(|v| v.is_object())\n        .unwrap_or(false)\n}","tryCatchPattern":"if let Err(err) = apply_relay_profile_to_home_with_switch_rules_and_computer_use_guard(&home, &profile, &common, guard) {\n    if err.to_string().contains(\"auth.json 必须是 JSON 对象\") {\n        // 当前版本该分支不可达；若出现说明运行的是旧二进制或代码被改动，先升级再排查\n    }\n}","preventionTips":["auth.json 始终保持 JSON 对象结构","手工编辑 auth.json 后用 JSON 校验","升级到当前版本可避免该旧版防御分支"],"tags":["auth-json","json","type-mismatch","codex-plus-core"],"backgroundTag":"json-type-mismatch","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}