Hmbown/CodeWhale · error

providers.openrouter.vendor must be an OpenRouter slug…

Error message

providers.openrouter.vendor must be an OpenRouter slug without whitespace or control characters

What it means

Thrown by `validate_openrouter_vendor` when the `providers.openrouter.vendor` config value contains whitespace or control characters. OpenRouter vendor values are slugs (e.g. "openai", "meta-llama") and must be clean identifier strings; this check runs after the empty/None cases.

Solutions

  1. Use the exact OpenRouter slug (lowercase, hyphen-separated, no spaces) e.g. "meta-llama".
  2. Trim the value and remove any embedded whitespace or control characters before setting it.
  3. Verify the vendor slug against OpenRouter's model listing before writing it to config.

Example fix

// before
[providers.openrouter]
vendor = "Open AI"
// after
[providers.openrouter]
vendor = "openai"
Defensive patterns

Strategy: validation

Validate before calling

fn valid_openrouter_vendor(v: &str) -> bool {
    !v.is_empty() && !v.chars().any(|c| c.is_whitespace() || c.is_control())
}

Try / catch

match set_provider_config_value(config, ProviderKind::Openrouter, ProviderConfigField::Vendor, value) {
    Err(e) if e.to_string().contains("without whitespace") => {
        let cleaned = value.trim();
        if valid_openrouter_vendor(cleaned) { retry_with(cleaned)? } else { return Err(e) }
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `set_provider_config_value` with `ProviderConfigField::Vendor` and a value containing spaces, tabs, newlines, quotes with surrounding whitespace, or control characters — e.g. `"open ai"`, `"openai\n"`.

Common situations: Pasting a vendor display name ("Open AI") instead of the slug ("openai") into config; a TOML value accidentally including a trailing newline or invisible control character from copy-paste.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at crates/config/src/lib.rs:147

    // Configured gateways use more credential dialects than the three headers
    // generated by Codewhale itself. `auth_mode = "none"` is an endpoint
    // contract, so suppress every credential-shaped request header instead of
    // allowing the same secret through Proxy-Authorization, X-Auth-Token,
    // X-Access-Token, X-Goog-Api-Key, or another *-token/*-api-key spelling.
    is_sensitive_config_key(name) || name.eq_ignore_ascii_case("cookie")
}

/// Preserve OpenRouter endpoint slugs verbatim; an empty value clears a pin.
/// The service owns the vendor catalog, so validation must not freeze one here.
pub fn validate_openrouter_vendor(value: &str) -> Result<Option<&str>> {
    if value.trim().is_empty() {
        return Ok(None);
    }
    if value
        .chars()
        .any(|ch| ch.is_whitespace() || ch.is_control())
    {
        bail!(
            "providers.openrouter.vendor must be an OpenRouter slug without whitespace or control characters"
        );
    }
    Ok(Some(value))
}

/// Apply a validated pin to an OpenRouter request without dropping unrelated
/// caller policies such as data collection or zero-data-retention constraints.
pub fn apply_openrouter_vendor(body: &mut serde_json::Value, vendor: Option<&str>) {
    if let Some(vendor) = vendor {
        if !body["provider"].is_object() {
            body["provider"] = serde_json::json!({});
        }
        body["provider"]["order"] = serde_json::json!([vendor]);
        body["provider"]["allow_fallbacks"] = serde_json::json!(false);
    }
}

View on GitHub (pinned to 73e0f67d83)