aaif-goose/goose · warning · anyhow::Error
Invalid provider id: {id}
Error message
Invalid provider id: {id} What it means
validate_provider_id (in declarative.rs's #[cfg(test)] module) rejects a provider id whose first character is not an ASCII lowercase letter, digit, or underscore. The id is the provider JSON's 'name' field, and this first-character rule is stricter than the rest of the string (a leading hyphen is not allowed even though hyphens are fine later). It fires from bundled-provider validation tests, not at runtime.
Source
Thrown at crates/goose-providers/src/declarative.rs:399
}
fn placeholder_var_names(template: &str) -> Vec<String> {
template
.split("${")
.skip(1)
.filter_map(|chunk| chunk.split_once('}'))
.map(|(name, _)| name.to_string())
.collect()
}
fn validate_provider_id(id: &str) -> Result<()> {
let mut chars = id.chars();
let Some(first) = chars.next() else {
anyhow::bail!("Invalid provider id: provider id cannot be empty");
};
if !(first.is_ascii_lowercase() || first.is_ascii_digit() || first == '_') {
anyhow::bail!("Invalid provider id: {id}");
}
if chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-')
{
Ok(())
} else {
anyhow::bail!("Invalid provider id: {id}")
}
}
#[test]
fn expose_declarative_providers_enumerates_all_bundled_json_files() {
let enumerated: HashSet<_> = fixed_provider_config_entries()
.into_iter()
.map(|(path, _)| path.to_string())
.collect();
let bundled: HashSet<_> = FIXED_PROVIDERS
.files()View on GitHub (pinned to 3810898a74)
Solutions
- Rename so the id starts with a lowercase letter, digit, or underscore: 'my-provider' instead of 'My-provider' or '-provider'
- If the display name needs capitals/style, keep 'name' slug-form and put the pretty text in 'display_name'
- Re-run the declarative provider tests after fixing
Example fix
// before
{ "name": "AcmeGateway", "display_name": "Acme", ... }
// after
{ "name": "acme_gateway", "display_name": "Acme Gateway", ... } Defensive patterns
Strategy: validation
Validate before calling
fn id_first_char_ok(id: &str) -> bool {
id.chars().next().is_some_and(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
} Type guard
fn is_valid_provider_id(id: &str) -> bool {
!id.is_empty()
&& id.starts_with(|c: char| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
&& id.chars().skip(1).all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')
} Try / catch
// In generator scripts, fix ids automatically instead of failing: let id = id.to_ascii_lowercase().trim_start_matches(['-', '.']).to_string(); assert!(is_valid_provider_id(&id));
Prevention
- Never start ids with a hyphen, dot, or uppercase letter; prefix-digit-underscore are the safe first chars
- Put branded capitalization in display_name, never in name/id
- Add a regex lint ([a-z0-9_][a-z0-9_-]*) to your provider-config linter
When it happens
Trigger: A bundled provider JSON with "name": "-gateway", "name": ".local", "name": "MyProvider" (uppercase first letter), or any leading symbol/punctuation — caught when cargo test runs all_bundled_providers_are_valid.
Common situations: Team naming conventions that start ids with uppercase brand names or hyphenated prefixes; porting provider ids from other ecosystems (npm scopes like '@acme/x' or '.local' style) that don't match goose's slug rules.
Related errors
- Invalid provider id: provider id cannot be empty
- Invalid provider type: {}
- Failed to parse {}: {}
- Provider '{}' has dynamic_models: false but no static models
- No messages found in scenario result
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/45a65482167920ef.
Report an issue: GitHub.