{"record":{"id":"d35cac9c0e026ea4","repo":"tinyhumansai/openhuman","slug":"no-usable-credentials-for-slug-which-openhuma-d35cac","errorCode":null,"errorMessage":"No usable credentials for '{slug}', which OpenHuman selected for the {} workload. Add a key for '{slug}' in Connections → LLM, set {}_provider to a provider that is configured, or enable the managed OpenHuman backend.","messagePattern":"No usable credentials for '(.+?)', which OpenHuman selected for the (.+?) workload\\. Add a key for '(.+?)' in Connections → LLM, set (.+?)_provider to a provider that is configured, or enable the managed OpenHuman backend\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/inference/provider/factory.rs","lineNumber":2287,"sourceCode":"    // 401 from the provider several layers later — exactly the baffling error\n    // this diagnostic exists to replace.\n    //\n    // Scoped to the *implicit fallback* path deliberately. That is the case the\n    // diagnostic is for: a local-chat user whose background role landed on a\n    // BYOK slug they never configured. An explicitly routed provider keeps its\n    // existing behaviour and is allowed to build without a stored key — callers\n    // construct such models to probe or describe a provider before a key is\n    // saved, and failing that at construction time would be a behaviour change\n    // well beyond this diagnostic.\n    //\n    // Styles that carry no stored key (`OpenhumanJwt` injects a session JWT\n    // downstream, `None` sends no auth header at all) are legitimately blank and\n    // never trip this.\n    if implicit_fallback\n        && key.trim().is_empty()\n        && matches!(entry.auth_style, AuthStyle::Bearer | AuthStyle::Anthropic)\n    {\n        anyhow::bail!(\"{}\", missing_credentials());\n    }\n    let bearer_is_oauth = slug == \"openai\" && openai_bearer_is_oauth(config);\n    let codex = resolve_openai_codex_routing(config, slug, &entry.endpoint, &key, bearer_is_oauth)\n        .map_err(anyhow::Error::msg)?;\n\n    Ok(CloudSlugResolution {\n        entry,\n        effective_model,\n        key,\n        codex,\n    })\n}\n\n/// A `<slug>:<model>` BYOK cloud provider as a crate-native [`ChatModel`] — the\n/// Native model for every configured cloud auth style, including the managed\n/// `OpenhumanJwt` entry (issue #4727 Phase 3).\n///\n/// Returns `None` unless the role resolves to a **configured** cloud slug. When","sourceCodeStart":2269,"sourceCodeEnd":2305,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/inference/provider/factory.rs#L2269-L2305","documentation":"Direct-selection credential failure: the resolved cloud slug (chosen explicitly, not via local-chat fallback — hence the shorter message without the local-model clause) has no usable API key, and its entry uses Bearer or Anthropic auth which requires one. Same code path as the fallback variant (factory.rs:~2287, `implicit_fallback && key.trim().is_empty()`), distinguished by the diagnostic template chosen.","triggerScenarios":"A role explicitly configured to a BYOK cloud slug (e.g. `anthropic:claude-sonnet-4-6`) where the stored key for that slug is empty, while the entry's auth_style is Bearer/Anthropic.","commonSituations":"Provider selected in Settings before its key was entered; key deleted or lost after a workspace/profile migration; key saved under a renamed slug.","solutions":["Enter the API key for '{slug}' in Connections → LLM.","Or switch `{role}_provider` to a slug that has a key, or to the managed OpenHuman backend.","Confirm the slug matches the Connections entry exactly (case/spacing).","If the key was just added, retry — resolution reads the credential store per request."],"exampleFix":"# config — before\nchat_provider = \"openai\"\n# (no key stored for openai)\n\n# after: either store the key via Connections -> LLM, or\nchat_provider = \"openhuman\"   # managed backend, no key needed","handlingStrategy":"validation","validationCode":"let key = auth.get_provider_bearer_token(slug, None)?.unwrap_or_default();\nif key.trim().is_empty() && matches!(entry.auth_style, AuthStyle::Bearer | AuthStyle::Anthropic) {\n    return Err(anyhow::anyhow!(\"missing key for {slug} — add it before selecting this provider\"));\n}","typeGuard":"fn provider_ready(slug: &str, entry: &CloudProviderEntry, auth: &AuthService) -> bool {\n    !matches!(entry.auth_style, AuthStyle::Bearer | AuthStyle::Anthropic)\n        || auth.get_provider_bearer_token(slug, None)\n               .ok().flatten()\n               .is_some_and(|k| !k.trim().is_empty())\n}","tryCatchPattern":null,"preventionTips":["Make provider selection validate key presence at save time, not request time.","Keep slug names identical between config and Connections entries.","Use the managed backend where no key management is desired."],"tags":["credentials","api-key","factory","provider-config"],"backgroundTag":"missing-api-key","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-23T11:17:13.642Z"}