{"record":{"id":"44a1d9991dd3703e","repo":"zeroclaw-labs/zeroclaw","slug":"openai-codex-endpoint-override-cannot-be-empty","errorCode":null,"errorMessage":"OpenAI Codex endpoint override cannot be empty","messagePattern":"OpenAI Codex endpoint override cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/openai_codex.rs","lineNumber":162,"sourceCode":"                .connect_timeout(std::time::Duration::from_secs(10))\n                .read_timeout(std::time::Duration::from_secs(300))\n                .build()\n                .unwrap_or_else(|_| Client::new()),\n        })\n    }\n}\n\nfn default_zeroclaw_dir() -> PathBuf {\n    directories::UserDirs::new().map_or_else(\n        || PathBuf::from(\".zeroclaw\"),\n        |dirs| dirs.home_dir().join(\".zeroclaw\"),\n    )\n}\n\nfn build_responses_url(base_or_endpoint: &str) -> anyhow::Result<String> {\n    let candidate = base_or_endpoint.trim();\n    if candidate.is_empty() {\n        anyhow::bail!(\"OpenAI Codex endpoint override cannot be empty\");\n    }\n\n    let mut parsed = reqwest::Url::parse(candidate).map_err(|_| {\n        ::zeroclaw_log::record!(\n            WARN,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(::serde_json::json!({\"candidate\": candidate})),\n            \"openai_codex: endpoint override is not a valid URL\"\n        );\n        anyhow::Error::msg(\"OpenAI Codex endpoint override must be a valid URL\")\n    })?;\n\n    match parsed.scheme() {\n        \"http\" | \"https\" => {}\n        _ => anyhow::bail!(\"OpenAI Codex endpoint override must use http:// or https://\"),\n    }\n","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/openai_codex.rs#L144-L180","documentation":"When an endpoint override (provider_api_url) is configured for the openai-codex provider, build_responses_url trims it and rejects an empty or whitespace-only value instead of silently falling back to the default Responses URL. The provider fails fast at construction time (new -> resolve_responses_url -> build_responses_url). The fix is to delete the override, not to blank it.","triggerScenarios":"Setting the codex provider's api_url/provider_api_url to \"\" or \"   \" in config or environment; a template config with an empty placeholder value that was deployed as-is.","commonSituations":"Copy-pasted config templates with empty url fields; setting an env var to empty string intending to 'unset' it; CI injecting an empty variable for the override.","solutions":["Remove the empty api_url/provider_api_url key entirely — resolve_responses_url then uses DEFAULT_CODEX_RESPONSES_URL.","Or set a real http(s) URL; a missing /responses suffix is appended automatically.","Lint config/env for empty-string endpoint overrides before deploy."],"exampleFix":"# before\n[providers.models.openai-codex.main]\napi_url = \"\"\n\n# after — key removed, default Responses endpoint is used\n[providers.models.openai-codex.main]\nmodel = \"gpt-5-codex\"","handlingStrategy":"validation","validationCode":"// Treat an unset override as absent, never as empty string\nlet api_url = configured_api_url.map(str::trim).filter(|s| !s.is_empty());\n// api_url == None now yields the default Responses URL","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Delete the api_url key to unset it — never write \"\"","Lint config templates for empty-string endpoint overrides before deploy","Fail CI when env-provided endpoint overrides are blank"],"tags":["openai-codex","config","url","validation","endpoint-override"],"backgroundTag":"invalid-endpoint-url","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}