tinyhumansai/openhuman · error

no injected channel model source for '{provider_name}'; prod

Error message

no injected channel model source for '{provider_name}'; production routes use crate-native model sources

What it means

`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`.

Source

Thrown at src/openhuman/channels/routes.rs:189

    provider_name: &str,
) -> anyhow::Result<crate::openhuman::agent::tinyagents::TurnModelSource> {
    if provider_name == ctx.default_provider.as_str() {
        return ctx.turn_model_source.as_ref().cloned().ok_or_else(|| {
            anyhow::anyhow!("no injected channel model source for '{provider_name}'")
        });
    }

    if let Some(existing) = ctx
        .turn_model_source_cache
        .lock()
        .unwrap_or_else(|e| e.into_inner())
        .get(provider_name)
        .cloned()
    {
        return Ok(existing);
    }

    anyhow::bail!(
        "no injected channel model source for '{provider_name}'; production routes use crate-native model sources"
    )
}

fn build_models_help_response(current: &ChannelRouteSelection, workspace_dir: &Path) -> String {
    let mut response = String::new();
    let _ = writeln!(
        response,
        "Current provider: `{}`\nCurrent model: `{}`",
        current.provider, current.model
    );
    response.push_str("\nSwitch model with `/model <model-id>`.\n");

    let cached_models = load_cached_model_preview(workspace_dir, &current.provider);
    if cached_models.is_empty() {
        let _ = writeln!(
            response,
            "\nNo cached model list found for `{}`. Ask the operator to refresh the model list in the web UI.",

View on GitHub (pinned to 7491200858)

Solutions

  1. 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.
  2. As the end user, run `/models` in the channel chat and switch to a provider that is configured.
  3. Clear the stale route override for the conversation (`/model` back to the default) if it points at a provider with no registered source.

Example fix

// before — context built without sources (test/embedding harness)
let ctx = ChannelRuntimeContext { config: None, ..Default::default() };

// after — inject the default + cached sources, or attach config
let mut cache = HashMap::new();
cache.insert("openai".to_string(), default_source.clone());
let ctx = ChannelRuntimeContext {
    config: None,
    default_provider: default_provider.clone(),
    turn_model_source: Some(default_source),
    turn_model_source_cache: Mutex::new(cache),
    ..Default::default()
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Before dispatching turns, assert the context can resolve every selectable provider
if ctx.config.is_none() {
    assert!(ctx.turn_model_source.is_some(),
        "no turn_model_source injected for default provider {}");
    let cache = ctx.turn_model_source_cache.lock().unwrap();
    for provider in selectable_route_providers(ctx) {
        assert!(cache.contains_key(provider),
            "no turn_model_source_cache entry for route provider {provider}");
    }
}

Try / catch

match get_or_create_turn_model_source(ctx.as_ref(), &route.provider).await {
    Ok(source) => Some(source),
    Err(err) => {
        crate::core::observability::report_error(
            &err, "channels", "provider_init",
            &[("channel", msg.channel.as_str()), ("provider", route.provider.as_str())],
        );
        // tell the user and skip the model call for this turn — do not retry blindly
        reply("Failed to initialize provider. Run /models to choose another provider.");
        None
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/82208131a482dd13. Report an issue: GitHub.