{"record":{"id":"53ee88a86cda188b","repo":"BigPizzaV3/CodexPlusPlus","slug":"auth-json-json","errorCode":null,"errorMessage":"auth.json 必须是 JSON 对象","messagePattern":"auth\\.json 必须是 JSON 对象","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":2796,"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 session_provider_id = active_provider_id(&doc);\n    let provider_id = if session_provider_id.as_deref() == Some(\"openai\") {\n        RELAY_PROVIDER.to_string()\n    } else {\n        active_or_default_provider_id(&doc)\n    };\n    let provider = ensure_provider_table(&mut doc, &provider_id)?;\n    if api_key.trim().is_empty() {\n        provider.remove(\"experimental_bearer_token\");","sourceCodeStart":2778,"sourceCodeEnd":2814,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/relay_config.rs#L2778-L2814","documentation":"When updating auth.json (e.g. to set OPENAI_API_KEY), the existing file content is parsed as JSON and must be an object at the top level. If the parsed value is an array, string, number, or other non-object JSON, the update bails because API-key injection only works on a JSON object.","triggerScenarios":"Calling the auth.json write/update path when ~/.codex/auth.json contains valid JSON that is not an object — e.g. the file holds a string, an array, or a bare number/bool/null.","commonSituations":"auth.json was overwritten by another tool with a non-object payload; a user pasted a token directly into auth.json (making it a JSON string); corrupted/partial file content that happens to still be valid JSON but the wrong shape.","solutions":["Reset auth.json to a JSON object: {} and retry the operation","Inspect the current auth.json content and fix its shape manually (keep credentials safe)","Delete only if you can re-login/re-provision credentials, then recreate the object shape","Check which other tool wrote the non-object content and stop it from clobbering auth.json"],"exampleFix":"// before (auth.json)\n\"sk-xxxx\"\n// after (auth.json)\n{\n  \"OPENAI_API_KEY\": \"sk-xxxx\"\n}","handlingStrategy":"type-guard","validationCode":"const parsed = JSON.parse(fs.readFileSync(authPath, 'utf8'));\nif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n  throw new Error('auth.json is not a JSON object; reset it to {} first');\n}","typeGuard":"const isJsonObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try { updateAuthKey(key); } catch (e) { if (String(e).includes('auth.json 必须是 JSON 对象')) { fs.writeFileSync(authPath, '{}\\n'); updateAuthKey(key); } else throw e; }","preventionTips":["Never write raw tokens directly into auth.json — always as an object field","Back up auth.json before other tools modify it","Check auth.json shape after any third-party tool touches ~/.codex"],"tags":["json","auth","config"],"backgroundTag":"invalid-json-response","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}