{"record":{"id":"4df38db34d186a9e","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-key-4df38d","errorCode":null,"errorMessage":"API Key 不能为空","messagePattern":"API Key 不能为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":534,"sourceCode":"        config_path: status.config_path,\n        backup_path,\n        configured: status.configured,\n    })\n}\n\npub async fn test_relay_profile(\n    profile: &RelayProfile,\n    model: &str,\n) -> anyhow::Result<RelayProfileTestResult> {\n    let base_url = relay_profile_base_url(profile);\n    let base_url = base_url.trim().trim_end_matches('/');\n    if base_url.is_empty() {\n        anyhow::bail!(\"Base URL 不能为空\");\n    }\n    let api_key = relay_profile_api_key(profile);\n    let api_key = api_key.trim();\n    if api_key.is_empty() {\n        anyhow::bail!(\"API Key 不能为空\");\n    }\n\n    let client = crate::http_client::proxied_client(\"CodexPlusPlus/RelayTest\")?;\n    let endpoint = match profile.protocol {\n        RelayProtocol::Responses => format!(\"{base_url}/responses\"),\n        RelayProtocol::ChatCompletions => format!(\"{base_url}/chat/completions\"),\n    };\n    let test_model = model.trim();\n    if test_model.is_empty() {\n        anyhow::bail!(\"测试模型不能为空\");\n    }\n\n    let payload = relay_profile_test_payload(profile.protocol, test_model);\n    let response = client\n        .post(&endpoint)\n        .bearer_auth(api_key)\n        .header(reqwest::header::CONTENT_TYPE, \"application/json\")\n        .json(&payload)","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_config.rs#L516-L552","documentation":"test_relay_profile 在 base_url 校验通过后校验 API Key：relay_profile_api_key(profile) trim 后为空则 bail，不发起网络请求。key 之后会作为 bearer_auth 头发送。","triggerScenarios":"调用 test_relay_profile 时 profile 的 api_key 解析结果为空（RelayProfile 的 api_key / auth_contents 相关字段均无可用值）。","commonSituations":"新建 profile 未填 Key 就点「测试连接」；key 存在 auth_contents JSON 里但字段名不是 OPENAI_API_KEY；配置迁移后 key 丢失。","solutions":["为 profile 填写 API Key 后再测试","确认 auth_contents JSON 中的键名为 OPENAI_API_KEY（relay_profile_api_key 识别的格式）","调用前本地做同样的 trim 校验，给用户表单级提示"],"exampleFix":"// before\nlet r = test_relay_profile(&profile_no_key, \"gpt-5\").await?;\n\n// after\nif relay_profile_api_key(&profile).trim().is_empty() {\n    return Err(anyhow::anyhow!(\"API Key 不能为空\"));\n}\nlet r = test_relay_profile(&profile, \"gpt-5\").await?;","handlingStrategy":"validation","validationCode":"if relay_profile_api_key(profile).trim().is_empty() {\n    return Err(anyhow::anyhow!(\"API Key 不能为空\"));\n}\ntest_relay_profile(profile, model).await?;","typeGuard":null,"tryCatchPattern":"match test_relay_profile(&profile, &model).await {\n    Ok(result) => { /* 展示连通性结果 */ }\n    Err(err) if err.to_string().contains(\"API Key 不能为空\") => { /* 提示补 Key */ }\n    Err(err) => return Err(err),\n}","preventionTips":["auth_contents 中的键名必须是 OPENAI_API_KEY","测试前与保存前共用同一套字段校验","key 从密钥管理器读取后先判空"],"tags":["relay","api-key","connectivity-test","validation"],"backgroundTag":"missing-api-key","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}