{"record":{"id":"e1436679302fb45d","repo":"aaif-goose/goose","slug":"invalid-provider-id-provider-id-cannot-be-empty-e14366","errorCode":null,"errorMessage":"Invalid provider id: provider id cannot be empty","messagePattern":"Invalid provider id: provider id cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose/src/config/declarative_providers.rs","lineNumber":100,"sourceCode":"        .to_string();\n    let base_id = format!(\"custom_{}\", normalized);\n\n    let custom_dir = custom_providers_dir();\n    let mut candidate_id = base_id.clone();\n    let mut counter = 1;\n\n    while custom_dir.join(format!(\"{}.json\", candidate_id)).exists() {\n        candidate_id = format!(\"{}_{}\", base_id, counter);\n        counter += 1;\n    }\n\n    candidate_id\n}\n\npub fn validate_provider_id(id: &str) -> Result<()> {\n    let mut chars = id.chars();\n    let Some(first) = chars.next() else {\n        return Err(anyhow::anyhow!(\n            \"Invalid provider id: provider id cannot be empty\"\n        ));\n    };\n\n    if !(first.is_ascii_lowercase() || first.is_ascii_digit() || first == '_') {\n        return Err(anyhow::anyhow!(\"Invalid provider id: {}\", id));\n    }\n\n    if chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-') {\n        Ok(())\n    } else {\n        Err(anyhow::anyhow!(\"Invalid provider id: {}\", id))\n    }\n}\n\nfn custom_provider_file_path(id: &str) -> Result<PathBuf> {\n    if id.is_empty()\n        || id","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose/src/config/declarative_providers.rs#L82-L118","documentation":"goose derives a provider id from a display name (generate_id) and validates it with validate_provider_id. The id must be non-empty and start with an ASCII lowercase letter, digit, or underscore. This variant is returned when the candidate id is the empty string — usually a display name that produced no usable characters after slug generation.","triggerScenarios":"Calling create_custom_provider with a display_name that is empty or contains only characters generate_id strips (spaces, punctuation), so the generated candidate id is \"\" and validate_provider_id fails on the first chars().next().","commonSituations":"Creating a custom provider named \"---\" or whitespace in the desktop UI; a script passing an unset/empty environment variable as display_name.","solutions":["Provide a display_name containing at least one ASCII letter or digit (e.g. \"Acme Gateway\")","If scripting, assert the display-name variable is non-empty and has an alphanumeric before calling create_custom_provider"],"exampleFix":"// before\nlet params = CreateCustomProviderParams { display_name: String::new(), ..Default::default() };\ncreate_custom_provider(params)?;\n\n// after\nlet params = CreateCustomProviderParams { display_name: \"acme-gateway\".into(), ..Default::default() };\ncreate_custom_provider(params)?;","handlingStrategy":"validation","validationCode":"// before create_custom_provider:\nlet candidate = generate_id(&params.display_name);\nassert!(!candidate.is_empty(), \"display name must yield a non-empty id\");","typeGuard":"fn is_valid_provider_id(id: &str) -> bool {\n    let mut chars = id.chars();\n    matches!(chars.next(), Some(c) if c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')\n        && chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')\n}","tryCatchPattern":null,"preventionTips":["Require display names to contain at least one ASCII alphanumeric before submission","Never pass raw user paths or punctuation-only strings as display names","Reuse ids produced by generate_id rather than hand-authoring them"],"tags":["providers","validation","custom-providers","id"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}