{"record":{"id":"db453acc2c12bdb8","repo":"Hmbown/CodeWhale","slug":"base-url-cannot-be-empty","errorCode":null,"errorMessage":"base URL cannot be empty","messagePattern":"base URL cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":12724,"sourceCode":"        identity.provider,\n        identity.persisted_id().unwrap_or(&identity.key),\n        model,\n    )\n}\n\n/// Persist a guided-setup endpoint choice into the provider's own\n/// `[providers.<name>] base_url` (#4526).\n///\n/// Deliberately narrow: it never touches the root `base_url`, another\n/// provider's table, or any other key, so a billing-route choice cannot\n/// repoint an unrelated route.\npub(crate) fn save_provider_base_url_for_identity(\n    identity: &ProviderIdentity,\n    _route_config: &Config,\n    base_url: &str,\n) -> Result<PathBuf> {\n    let base_url = base_url.trim();\n    anyhow::ensure!(!base_url.is_empty(), \"base URL cannot be empty\");\n    let config_path = try_default_config_path()\n        .context(\"Failed to resolve config path for provider base URL.\")?;\n    ensure_parent_dir(&config_path)?;\n    let key_inside = if identity.provider == ApiProvider::Custom {\n        let key = identity.key.trim();\n        anyhow::ensure!(!key.is_empty(), \"custom provider id cannot be empty\");\n        key\n    } else {\n        provider_config_key(identity.provider).context(\"provider base URL table\")?\n    };\n    crate::config_persistence::mutate_config_document(&config_path, |doc| {\n        crate::config_persistence::set_document_value(\n            doc,\n            &[\"providers\", key_inside, \"base_url\"],\n            base_url,\n        )\n    })\n    .with_context(|| format!(\"Failed to write config to {}\", config_path.display()))?;","sourceCodeStart":12706,"sourceCodeEnd":12742,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config.rs#L12706-L12742","documentation":"save_provider_base_url_for_identity trims and slash-trims the guided-setup endpoint and rejects an empty value; a provider's [providers.<name>] base_url must be a non-empty URL, and the narrow writer never touches the root base_url.","triggerScenarios":"Calling `save_provider_base_url_for_identity` with `base_url` empty or whitespace-only after trim.","commonSituations":"User cleared the base-URL field and saved; migration imported a config with an empty URL; script variable unset before the call.","solutions":["Pass a complete base URL (e.g. `https://api.example.com/v1`) to the routine","Validate the URL is non-empty and parses before calling save","To return to a provider's default endpoint, remove the override instead of saving an empty value"],"exampleFix":"// before\nsave_provider_base_url_for_identity(&identity, &cfg, \"\")?;\n// after\nlet url = input.trim();\nanyhow::ensure!(!url.is_empty(), \"base URL required\");\nsave_provider_base_url_for_identity(&identity, &cfg, url)?;","handlingStrategy":"validation","validationCode":"let url = base_url.trim();\nif url.is_empty() { return Err(anyhow::anyhow!(\"base URL required\")); }","typeGuard":null,"tryCatchPattern":"if let Err(e) = save_provider_base_url_for_identity(&identity, &cfg, u) {\n    if e.to_string().contains(\"base URL cannot be empty\") {\n        // clear the override via the delete path instead\n    }\n}","preventionTips":["Validate URLs parse (e.g. `url::Url::parse`) before saving","Treat clearing the field as 'remove override', not save-empty","Pre-fill the provider's default endpoint in forms"],"tags":["validation","base-url","empty-input"],"backgroundTag":"empty-required-field","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}