{"record":{"id":"16427a1ab1f64bb6","repo":"zeroclaw-labs/zeroclaw","slug":"api-key-prefix-mismatch-key-visible-looks","errorCode":null,"errorMessage":"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}`.","messagePattern":"API key prefix mismatch: key \"(.+?)\\.\\.\\.\" looks like a (.+?) key, but model_provider \"(.+?)\" is selected\\. Set the correct provider-specific env var or use `-p (.+?)`\\.","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/lib.rs","lineNumber":1326,"sourceCode":"            options.vision,\n        ));\n    }\n    let resolved_credential = resolve_model_provider_credential(provider_kind, api_key)\n        .map(|v| String::from_utf8(v.into_bytes()).unwrap_or_default());\n    #[allow(clippy::option_as_ref_deref)]\n    let key = resolved_credential.as_ref().map(String::as_str);\n\n    // Pre-flight: catch obvious API-key / model_provider mismatches early.\n    if let Some(key_value) = key {\n        let is_custom =\n            provider_kind.starts_with(\"custom:\") || provider_kind.starts_with(\"anthropic-custom:\");\n        let has_custom_url = api_url.map(str::trim).filter(|u| !u.is_empty()).is_some();\n        if !is_custom\n            && !has_custom_url\n            && let Some(likely_model_provider) = check_api_key_prefix(provider_kind, key_value)\n        {\n            let visible = &key_value[..key_value.len().min(8)];\n            anyhow::bail!(\n                \"API key prefix mismatch: key \\\"{visible}...\\\" looks like a \\\n                     {likely_model_provider} key, but model_provider \\\"{provider_kind}\\\" is selected. \\\n                     Set the correct provider-specific env var or use `-p {likely_model_provider}`.\"\n            );\n        }\n    }\n\n    // Resolve the effective endpoint URL for the dispatch arms below.\n    // Precedence: `api_url` parameter (operator-set base.uri), then\n    // `options.provider_api_url` (pre-resolved family endpoint URI from the\n    // typed alias's `*Endpoint::uri()` for multi-endpoint families).\n    let resolved_url: Option<&str> =\n        api_url\n            .map(str::trim)\n            .filter(|v| !v.is_empty())\n            .or_else(|| {\n                options\n                    .provider_api_url","sourceCodeStart":1308,"sourceCodeEnd":1344,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/lib.rs#L1308-L1344","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nexport OPENAI_API_KEY=sk-ant-api03-xxxx\nzeroclaw -p openai ...\n\n# after\nexport ANTHROPIC_API_KEY=sk-ant-api03-xxxx\nzeroclaw -p anthropic ...","handlingStrategy":"validation","validationCode":"fn key_matches_provider(provider_kind: &str, key: &str) -> bool {\n    let likely = if key.starts_with(\"sk-ant-\") { \"anthropic\" }\n        else if key.starts_with(\"sk-or-\") { \"openrouter\" }\n        else if key.starts_with(\"gsk_\") { \"groq\" }\n        else { return true };\n    provider_kind == likely || provider_kind.contains(\"custom\") || provider_kind.contains(':')\n}","typeGuard":"fn sniff_key_provider(key: &str) -> Option<&'static str> {\n    match key {\n        k if k.starts_with(\"sk-ant-\") => Some(\"anthropic\"),\n        k if k.starts_with(\"sk-or-\") => Some(\"openrouter\"),\n        k if k.starts_with(\"gsk_\") => Some(\"groq\"),\n        _ => None,\n    }\n}","tryCatchPattern":"if let Some(likely) = sniff_key_provider(&key) {\n    if likely != provider_kind {\n        return Err(format!(\"key looks like {likely}; set {likely}_API_KEY or pass -p {likely}\"));\n    }\n}\ncreate_resilient_model_provider(name, Some(&key), None, &reliability).await","preventionTips":["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"],"tags":["api-key","credentials","prefix-mismatch","configuration"],"backgroundTag":"api-key-provider-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}