Hmbown/CodeWhale · error

custom provider name must not shadow a built-in provider

Error message

custom provider name must not shadow a built-in provider

What it means

A custom provider name must not collide with a built-in provider. normalize_custom_provider_id checks crate::config::ApiProvider::parse and bails if the name resolves to a known built-in provider, preventing shadowing of built-in config keys.

Solutions

  1. Choose a distinct name not matching any built-in provider id (e.g. "openai_proxy").
  2. Check the name against ApiProvider::parse in the UI before submitting.

Example fix

// before
persist_custom_provider(path, "openai", &url, ...)?;
// after
persist_custom_provider(path, "openai_proxy", &url, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

if crate::config::ApiProvider::parse(name.trim()).is_some() { return Err(anyhow::anyhow!("name shadows a built-in provider")); }

Type guard

fn shadows_builtin(name: &str) -> bool { crate::config::ApiProvider::parse(name.trim()).is_some() }

Try / catch

match persist_custom_provider(path, name, &url, ...) {
    Err(e) if e.to_string().contains("shadow") => suggest_alternate_name(name),
    other => other,
}

Prevention

When it happens

Trigger: Calling persist_custom_provider with a name like "openai", "anthropic", or "deepseek" that parses as a built-in ApiProvider.

Common situations: A user names a custom gateway "openai" to point it at a proxy; the persistence layer refuses to create a shadowing table.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/51db042007667bd0. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/config_persistence.rs:696

            }
        }
        Ok(())
    })?;
    Ok(path)
}

fn normalize_custom_provider_id(raw: &str) -> anyhow::Result<String> {
    use anyhow::bail;

    let value = raw.trim();
    if value.is_empty() {
        bail!("custom provider name is required");
    }
    if value == "__custom__" {
        bail!("custom provider name is reserved");
    }
    if crate::config::ApiProvider::parse(value).is_some() {
        bail!("custom provider name must not shadow a built-in provider");
    }
    if !value
        .chars()
        .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-'))
    {
        bail!("custom provider name may only use letters, numbers, '-' and '_'");
    }
    Ok(value.to_string())
}

fn normalize_custom_provider_base_url(raw: &str) -> anyhow::Result<String> {
    use anyhow::bail;

    let value = raw.trim().trim_end_matches('/');
    if value.is_empty() {
        bail!("custom provider base URL is required");
    }
    let parsed = reqwest::Url::parse(value)

View on GitHub (pinned to 73e0f67d83)