{"record":{"id":"36848f335303dc81","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-auth-json-openai-api-key","errorCode":null,"errorMessage":"官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth.json 后再切换。","messagePattern":"官方混合 API 不应在 auth\\.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth\\.json 后再切换。","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_switch.rs","lineNumber":185,"sourceCode":"                profile.id.as_str()\n            } else {\n                profile.name.as_str()\n            }\n        );\n    }\n    if profile.relay_mode == RelayMode::Official\n        && serde_json::from_str::<serde_json::Value>(&profile.auth_contents)\n            .ok()\n            .and_then(|value| {\n                value\n                    .get(\"OPENAI_API_KEY\")\n                    .and_then(serde_json::Value::as_str)\n                    .map(str::trim)\n                    .map(str::is_empty)\n            })\n            == Some(false)\n    {\n        anyhow::bail!(\n            \"官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth.json 后再切换。\"\n        );\n    }\n    Ok(())\n}\n\nfn relay_combined_common_config(settings: &BackendSettings) -> String {\n    let sections = [\n        settings.relay_common_config_contents.trim(),\n        settings.relay_context_config_contents.trim(),\n    ]\n    .into_iter()\n    .filter(|section| !section.is_empty())\n    .collect::<Vec<_>>();\n    if sections.is_empty() {\n        String::new()\n    } else {\n        crate::relay_config::normalize_config_text(&format!(\"{}\\n\", sections.join(\"\\n\\n\")))","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/relay_switch.rs#L167-L203","documentation":"For profiles configured as official hybrid API (官方混合 API), validate_switch_profile_files checks the profile's auth.json contents and fails the switch if an OPENAI_API_KEY entry is present and non-empty. Hybrid-mode auth must come from ChatGPT login tokens, not a stored API key.","triggerScenarios":"Calling apply_selected_relay_profile on a hybrid-API profile whose auth_contents JSON contains OPENAI_API_KEY set to a non-empty string.","commonSituations":"User pasted an auth.json exported from an API-key-based provider into a hybrid profile; profile was converted from API-key mode to hybrid mode without cleaning auth.json.","solutions":["Remove the OPENAI_API_KEY field (or empty its value) from this provider's auth.json, then retry the switch","If the provider actually uses API-key auth, change relay_mode away from the official hybrid API mode"],"exampleFix":"// before\n{ \"OPENAI_API_KEY\": \"sk-...\", \"tokens\": null }\n// after\n{ \"OPENAI_API_KEY\": \"\", \"tokens\": null }","handlingStrategy":"validation","validationCode":"fn hybrid_auth_ok(auth_json: &str) -> bool {\n    serde_json::from_str::<serde_json::Value>(auth_json)\n        .ok()\n        .and_then(|v| v.get(\"OPENAI_API_KEY\").and_then(serde_json::Value::as_str).map(str::trim).map(|s| s.is_empty()))\n        .unwrap_or(true)\n}","typeGuard":null,"tryCatchPattern":"match apply_selected_relay_profile(&profile) {\n    Err(e) if e.to_string().contains(\"官方混合 API\") => {\n        clean_openai_api_key_from_auth(&mut profile);\n        retry(profile)\n    }\n    other => other,\n}","preventionTips":["When converting a profile to hybrid mode, automatically strip OPENAI_API_KEY from auth.json","Never paste auth.json from API-key providers into hybrid profiles","Audit stored auth.json files for stray OPENAI_API_KEY entries"],"tags":["auth","config","validation"],"backgroundTag":"invalid-config-value","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"}