zeroclaw-labs/zeroclaw · error · anyhow::Error
API key prefix mismatch: key "{visible}..." looks like a {li
Error message
API key prefix mismatch: key "{visible}..." looks like a {likely_model_provider} key, but model_provider "{provider_kind}" is selected. Set the correct provider-specific env var or use `-p {likely_model_provider}`. What it means
The provider factory runs a pre-flight key sniff: when the resolved API key's prefix matches a known provider fingerprint (check_api_key_prefix) that differs from the selected provider_kind, and the provider is neither a custom/anthropic-custom name nor has a custom api_url, construction fails instead of sending a guaranteed-to-be-rejected request. The message shows the first 8 characters of the key and the likely intended provider.
Source
Thrown at crates/zeroclaw-providers/src/lib.rs:1326
options.vision,
));
}
let resolved_credential = resolve_model_provider_credential(provider_kind, api_key)
.map(|v| String::from_utf8(v.into_bytes()).unwrap_or_default());
#[allow(clippy::option_as_ref_deref)]
let key = resolved_credential.as_ref().map(String::as_str);
// Pre-flight: catch obvious API-key / model_provider mismatches early.
if let Some(key_value) = key {
let is_custom =
provider_kind.starts_with("custom:") || provider_kind.starts_with("anthropic-custom:");
let has_custom_url = api_url.map(str::trim).filter(|u| !u.is_empty()).is_some();
if !is_custom
&& !has_custom_url
&& let Some(likely_model_provider) = check_api_key_prefix(provider_kind, key_value)
{
let visible = &key_value[..key_value.len().min(8)];
anyhow::bail!(
"API key prefix mismatch: key \"{visible}...\" looks like a \
{likely_model_provider} key, but model_provider \"{provider_kind}\" is selected. \
Set the correct provider-specific env var or use `-p {likely_model_provider}`."
);
}
}
// Resolve the effective endpoint URL for the dispatch arms below.
// Precedence: `api_url` parameter (operator-set base.uri), then
// `options.provider_api_url` (pre-resolved family endpoint URI from the
// typed alias's `*Endpoint::uri()` for multi-endpoint families).
let resolved_url: Option<&str> =
api_url
.map(str::trim)
.filter(|v| !v.is_empty())
.or_else(|| {
options
.provider_api_urlView on GitHub (pinned to 88bb9c8533)
Solutions
- Put the key in the matching provider's env var (e.g. ANTHROPIC_API_KEY for sk-ant- keys) or select the provider with `-p <likely provider>`
- If the key genuinely fronts a gateway whose prefix mimics another provider, set `uri`/api_url on the alias so the pre-flight is skipped
- Re-copy the key: truncated pastes sometimes produce a misleading prefix
Example fix
# before export OPENAI_API_KEY=sk-ant-api03-xxxx zeroclaw -p openai ... # after export ANTHROPIC_API_KEY=sk-ant-api03-xxxx zeroclaw -p anthropic ...
Defensive patterns
Strategy: validation
Validate before calling
fn key_matches_provider(provider_kind: &str, key: &str) -> bool {
let likely = if key.starts_with("sk-ant-") { "anthropic" }
else if key.starts_with("sk-or-") { "openrouter" }
else if key.starts_with("gsk_") { "groq" }
else { return true };
provider_kind == likely || provider_kind.contains("custom") || provider_kind.contains(':')
} Type guard
fn sniff_key_provider(key: &str) -> Option<&'static str> {
match key {
k if k.starts_with("sk-ant-") => Some("anthropic"),
k if k.starts_with("sk-or-") => Some("openrouter"),
k if k.starts_with("gsk_") => Some("groq"),
_ => None,
}
} Try / catch
if let Some(likely) = sniff_key_provider(&key) {
if likely != provider_kind {
return Err(format!("key looks like {likely}; set {likely}_API_KEY or pass -p {likely}"));
}
}
create_resilient_model_provider(name, Some(&key), None, &reliability).await Prevention
- Bind each provider's key to its own env var instead of one generic variable
- Run the prefix sniff in CI before starting long jobs
- For gateway keys that mimic provider prefixes, set a custom uri so the pre-flight is bypassed
When it happens
Trigger: provider_kind = openai while the key starts with sk-ant- (Anthropic); `-p openai` combined with an Anthropic key in the generic env var; a groq-style gsk_ key resolved for a different family. Skipped entirely for custom:<url> names or when api_url/uri is set.
Common situations: Copy-pasting the wrong key into the generic or per-provider env var; switching providers with -p while keeping the old env var; CI secret matrices where one job binds key A to provider B.
Related errors
- providers.models.ollama.{alias}.model uses ':cloud', but no
- GLM API key not set or invalid format. Expected 'id.secret'.
- Fallback provider `{raw}` resolved to `{resolved}` ({profile
- {var_name} not found or empty in .env
- unknown credential_class `{class}`; expected encrypted_secre
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/16427a1ab1f64bb6.
Report an issue: GitHub.