{"record":{"id":"1da5718ff11cb388","repo":"BigPizzaV3/CodexPlusPlus","slug":"base-url-1da571","errorCode":null,"errorMessage":"中转 Base URL 不能为空","messagePattern":"中转 Base URL 不能为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":401,"sourceCode":"        base_url,\n        bearer_token,\n        protocol,\n        proxy_port,\n        RelaySessionProvider::Custom,\n    )\n}\n\npub fn apply_relay_config_to_home_with_session_provider(\n    home: &Path,\n    base_url: &str,\n    bearer_token: &str,\n    protocol: RelayProtocol,\n    proxy_port: u16,\n    session_provider: RelaySessionProvider,\n) -> anyhow::Result<RelayApplyResult> {\n    let base_url = base_url.trim();\n    if base_url.is_empty() {\n        anyhow::bail!(\"中转 Base URL 不能为空\");\n    }\n    let bearer_token = bearer_token.trim();\n    if bearer_token.is_empty() {\n        anyhow::bail!(\"中转 Key 不能为空\");\n    }\n    if session_provider == RelaySessionProvider::Openai && protocol != RelayProtocol::Responses {\n        anyhow::bail!(\"OpenAI 会话身份仅支持 Responses API\");\n    }\n    let codex_base_url = codex_base_url_for_protocol(base_url, protocol, proxy_port);\n    let updated = upsert_model_provider_config_with_session_provider(\n        \"\",\n        &codex_base_url,\n        bearer_token,\n        true,\n        session_provider,\n    )?;\n    let auth_contents = serde_json::to_string_pretty(&json!({\n        \"OPENAI_API_KEY\": bearer_token","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/relay_config.rs#L383-L419","documentation":"Thrown by `apply_relay_config_to_home_with_session_provider` in relay_config.rs when the `base_url` argument is empty after trimming. This function writes the relay provider entry into the codex home config.toml; without a base URL the generated `model_provider` would have no endpoint, so the apply aborts before touching files. It is the first validation in the apply pipeline.","triggerScenarios":"Calling `apply_relay_config_to_home_with_protocol` / `apply_relay_config_to_home_with_session_provider` with `base_url = \"\"` or `\"   \"`; bails at relay_config.rs:401 before bearer-token and session-provider checks.","commonSituations":"A relay profile was saved without an endpoint; a UI passed an empty string because the profile field was never filled; refactoring code that previously defaulted the URL now passes an empty value.","solutions":["Pass the relay endpoint (e.g. \"https://relay.example.com\") as base_url.","Load and validate the saved profile before applying; reject blank base_url in the UI.","If a default endpoint exists for the protocol, substitute it before calling the apply function."],"exampleFix":"// before\napply_relay_config_to_home_with_protocol(home, \"\", key, protocol, port, sp).await?;\n// after\napply_relay_config_to_home_with_protocol(home, \"https://relay.example.com\", key, protocol, port, sp).await?;","handlingStrategy":"validation","validationCode":"let base_url = base_url.trim();\nanyhow::ensure!(!base_url.is_empty(), \"中转 Base URL 不能为空\");\nanyhow::ensure!(base_url.starts_with(\"http\"), \"中转 Base URL 必须是 http(s) URL\");","typeGuard":"fn valid_base_url(url: &str) -> bool {\n    let u = url.trim(); u.starts_with(\"http://\") || u.starts_with(\"https://\")\n}","tryCatchPattern":"match apply_relay_config_to_home_with_protocol(home, url, key, proto, port, sp).await {\n    Ok(r) => info!(\"applied: {:?}\", r),\n    Err(e) if e.to_string().contains(\"中转 Base URL 不能为空\") => warn!(\"profile missing endpoint; skipping apply\"),\n    Err(e) => return Err(e),\n}","preventionTips":["Load relay profiles through a validation layer that rejects blank endpoints at save time.","Resolve defaults per protocol before calling apply.","Write an integration test that applies each stored profile to catch blank-field regressions."],"tags":["validation","relay-config","empty-field","url"],"backgroundTag":"empty-required-field","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"}