{"record":{"id":"82208131a482dd13","repo":"tinyhumansai/openhuman","slug":"no-injected-channel-model-source-for-provider-na","errorCode":null,"errorMessage":"no injected channel model source for '{provider_name}'; production routes use crate-native model sources","messagePattern":"no injected channel model source for '(.+?)'; production routes use crate-native model sources","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/channels/routes.rs","lineNumber":189,"sourceCode":"    provider_name: &str,\n) -> anyhow::Result<crate::openhuman::agent::tinyagents::TurnModelSource> {\n    if provider_name == ctx.default_provider.as_str() {\n        return ctx.turn_model_source.as_ref().cloned().ok_or_else(|| {\n            anyhow::anyhow!(\"no injected channel model source for '{provider_name}'\")\n        });\n    }\n\n    if let Some(existing) = ctx\n        .turn_model_source_cache\n        .lock()\n        .unwrap_or_else(|e| e.into_inner())\n        .get(provider_name)\n        .cloned()\n    {\n        return Ok(existing);\n    }\n\n    anyhow::bail!(\n        \"no injected channel model source for '{provider_name}'; production routes use crate-native model sources\"\n    )\n}\n\nfn build_models_help_response(current: &ChannelRouteSelection, workspace_dir: &Path) -> String {\n    let mut response = String::new();\n    let _ = writeln!(\n        response,\n        \"Current provider: `{}`\\nCurrent model: `{}`\",\n        current.provider, current.model\n    );\n    response.push_str(\"\\nSwitch model with `/model <model-id>`.\\n\");\n\n    let cached_models = load_cached_model_preview(workspace_dir, &current.provider);\n    if cached_models.is_empty() {\n        let _ = writeln!(\n            response,\n            \"\\nNo cached model list found for `{}`. Ask the operator to refresh the model list in the web UI.\",","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/channels/routes.rs#L171-L207","documentation":"`get_or_create_turn_model_source` resolves the `TurnModelSource` for a chat turn from the runtime context's injected sources: the default provider requires `ctx.turn_model_source` to be set, any other provider requires an entry in `ctx.turn_model_source_cache`. If neither is present it bails — the function only ever returns injected sources; production builds crate-native model sources elsewhere and should not reach this path. The dispatcher (runtime/dispatch/processor.rs:236) only calls it when `ctx.config.is_none()` and on error reports it to observability as `provider_init` and tells the user to run `/models`.","triggerScenarios":"Dispatching a channel message with a runtime context that has no `config` and no injected source for the active route provider — e.g. a test or embedding harness constructing `ChannelRuntimeContext` without wiring `turn_model_source`/`turn_model_source_cache`, or a per-conversation `/model` route override pointing at a provider whose source was never registered.","commonSituations":"Embedding the channel host in tests without injecting model sources; stale route overrides (sender_key → provider) left over after the provider set changed; refactors that stopped wiring the default source into the context.","solutions":["Wire the context before dispatch: set `turn_model_source` for the default provider and pre-populate `turn_model_source_cache` for every provider a route can select — or attach `config` so the crate-native model-source path is used.","As the end user, run `/models` in the channel chat and switch to a provider that is configured.","Clear the stale route override for the conversation (`/model` back to the default) if it points at a provider with no registered source."],"exampleFix":"// before — context built without sources (test/embedding harness)\nlet ctx = ChannelRuntimeContext { config: None, ..Default::default() };\n\n// after — inject the default + cached sources, or attach config\nlet mut cache = HashMap::new();\ncache.insert(\"openai\".to_string(), default_source.clone());\nlet ctx = ChannelRuntimeContext {\n    config: None,\n    default_provider: default_provider.clone(),\n    turn_model_source: Some(default_source),\n    turn_model_source_cache: Mutex::new(cache),\n    ..Default::default()\n};","handlingStrategy":"try-catch","validationCode":"// Before dispatching turns, assert the context can resolve every selectable provider\nif ctx.config.is_none() {\n    assert!(ctx.turn_model_source.is_some(),\n        \"no turn_model_source injected for default provider {}\");\n    let cache = ctx.turn_model_source_cache.lock().unwrap();\n    for provider in selectable_route_providers(ctx) {\n        assert!(cache.contains_key(provider),\n            \"no turn_model_source_cache entry for route provider {provider}\");\n    }\n}","typeGuard":null,"tryCatchPattern":"match get_or_create_turn_model_source(ctx.as_ref(), &route.provider).await {\n    Ok(source) => Some(source),\n    Err(err) => {\n        crate::core::observability::report_error(\n            &err, \"channels\", \"provider_init\",\n            &[(\"channel\", msg.channel.as_str()), (\"provider\", route.provider.as_str())],\n        );\n        // tell the user and skip the model call for this turn — do not retry blindly\n        reply(\"Failed to initialize provider. Run /models to choose another provider.\");\n        None\n    }\n}","preventionTips":["When embedding the channel host, inject turn_model_source (default provider) and pre-populate turn_model_source_cache for every provider a route can select","Attach config to the runtime context in production so crate-native model sources are used instead of injected ones","Route overrides persist per sender_key — clear them when the provider set changes"],"tags":["channels","model-routing","provider-init","runtime-context","dependency-injection"],"backgroundTag":"missing-provider-registration","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}