{"record":{"id":"e35dbcf64045fcd5","repo":"zeroclaw-labs/zeroclaw","slug":"openaicompatiblebuilder-base-url-is-required","errorCode":null,"errorMessage":"OpenAiCompatibleBuilder: base_url() is required","messagePattern":"OpenAiCompatibleBuilder: base_url\\(\\) is required","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/compatible.rs","lineNumber":555,"sourceCode":"        self.auth_profile_override = profile_override;\n        self\n    }\n\n    /// Finalize the builder into a ready provider. Every optional construction\n    /// value must be set on this builder; the returned provider has no\n    /// post-construction mutators.\n    ///\n    /// # Panics\n    /// Panics if [`Self::display_name`], [`Self::base_url`], or\n    /// [`Self::auth_style`] was not called — those three fields carry no\n    /// sensible default and every real call site sets them.\n    pub fn build(self) -> OpenAiCompatibleModelProvider {\n        let name = self\n            .name\n            .expect(\"OpenAiCompatibleBuilder: display_name() is required\");\n        let base_url = self\n            .base_url\n            .expect(\"OpenAiCompatibleBuilder: base_url() is required\");\n        let auth_style = self\n            .auth_style\n            .expect(\"OpenAiCompatibleBuilder: auth_style() is required\");\n        // Either merge preset can enable the shared merge behavior.\n        let merge_system_into_user =\n            self.merge_system_into_user || self.merge_system_into_user_preserve_native;\n        // Default `native_tool_calling` is `!merge_system_into_user_disable_native`,\n        // i.e. only the \"combined preset\" builder setter disables it. The\n        // explicit `without_native_tools()` override wins if present.\n        let native_tool_calling = self\n            .native_tool_calling_override\n            .unwrap_or(!self.merge_system_into_user);\n        // Read the PEM bytes now so later HTTP clients incur no per-request I/O.\n        // A read error is logged at WARN and TLS falls back to system roots —\n        // preserving the established warning-and-fallback semantics.\n        let tls_ca_cert_pem =\n            self.tls_ca_cert_path\n                .as_deref()","sourceCodeStart":537,"sourceCodeEnd":573,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/compatible.rs#L537-L573","documentation":"OpenAiCompatibleBuilder is the builder for OpenAI-compatible model providers in zeroclaw-providers. Its build() method treats display_name, base_url, and auth_style as mandatory setters and panics via expect() when any was skipped. This panic means .base_url() was never called before .build(), i.e. the builder contract was violated.","triggerScenarios":"Calling OpenAiCompatibleModelProvider::builder()...build() (directly or through http_client(), streaming_http_client(), or make_model_provider()) without a prior .base_url(url) call. Typically the setter is skipped because the URL comes from config/env and an if-let around it silently falls through.","commonSituations":"Adding a new OpenAI-compatible provider integration and wiring api_key/model but forgetting the endpoint URL; a config field rename (e.g. endpoint vs base_url) leaving the setter unpopulated; an absent env var making a conditional setter call not execute.","solutions":["Call .base_url(url) on the builder before .build() (and verify display_name() and auth_style() are also set, they panic the same way).","Trace the failing call through make_model_provider/http_client/streaming_http_client to find where the base URL should be injected and why it is None.","If the URL comes from env/config, validate its presence early and return a descriptive error instead of letting build() panic.","Add a construction unit test for every new provider preset so a missing setter fails in CI, not at runtime."],"exampleFix":"// before\nlet provider = OpenAiCompatibleModelProvider::builder()\n    .display_name(\"my-llm\")\n    .auth_style(AuthStyle::Bearer)\n    .build(); // panics: base_url() is required\n\n// after\nlet provider = OpenAiCompatibleModelProvider::builder()\n    .display_name(\"my-llm\")\n    .base_url(\"https://api.example.com/v1\")\n    .auth_style(AuthStyle::Bearer)\n    .build();","handlingStrategy":"validation","validationCode":"// Before building, resolve the endpoint URL and fail with a clear error:\nlet base_url = std::env::var(\"MY_PROVIDER_BASE_URL\")\n    .or_else(|_| config.provider_endpoint.clone().ok_or_else(|| anyhow::anyhow!(\"base URL missing\")))?;\nlet provider = OpenAiCompatibleModelProvider::builder()\n    .display_name(\"my-llm\")\n    .base_url(base_url)\n    .auth_style(AuthStyle::Bearer)\n    .build();","typeGuard":null,"tryCatchPattern":"// Embedding code that must not abort:\nlet provider = std::panic::catch_unwind(|| builder.build())\n    .map_err(|_| anyhow::anyhow!(\"provider builder contract violated: display_name/base_url/auth_style required\"))?;","preventionTips":["Treat display_name, base_url, and auth_style as one mandatory bundle; set them adjacently in every construction site.","Resolve all three from config up front and error with a field-level message instead of conditionally calling setters.","Add a construction unit test per provider preset so missing setters fail in CI."],"tags":["rust","builder","panic","provider","openai-compatible","config"],"backgroundTag":"builder-missing-required-field","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}