tinyhumansai/openhuman · warning · anyhow::Error

filter provider '{}' does not match source provider '{}'

Error message

filter provider '{}' does not match source provider '{}'

What it means

Contract check in task_sources::store::add_source: FilterSpec is a provider-tagged enum (github|notion|linear|clickup) and its tag must equal the provider argument of the source being created. The store refuses to persist a source whose filter targets a different provider, because every downstream fetcher resolves the provider registry from TaskSource.provider and would misinterpret the filter.

Source

Thrown at src/openhuman/integrations/task_sources/store.rs:73

    );
    let digest = Sha256::digest(canonical.as_bytes());
    format!("{digest:x}")
}

/// Insert a new task source.
#[allow(clippy::too_many_arguments)]
pub fn add_source(
    config: &Config,
    provider: ProviderSlug,
    connection_id: Option<String>,
    name: Option<String>,
    filter: FilterSpec,
    interval_secs: u64,
    target: SourceTarget,
    max_tasks_per_fetch: u32,
) -> Result<TaskSource> {
    if filter.provider() != provider {
        anyhow::bail!(
            "filter provider '{}' does not match source provider '{}'",
            filter.provider().as_str(),
            provider.as_str()
        );
    }
    // Normalize blank optional fields to NULL so a whitespace-only
    // connection_id can't masquerade as a real selector (mirrors
    // `update_source`).
    let connection_id = connection_id.filter(|s| !s.trim().is_empty());
    let name = name.filter(|s| !s.trim().is_empty());
    let id = Uuid::new_v4().to_string();
    let now = Utc::now();
    let filter_json = serde_json::to_string(&filter).context("serialize task source filter")?;
    let target_json = serde_json::to_string(&target).context("serialize task source target")?;
    let interval_i64 = i64::try_from(interval_secs)
        .context("task source interval_secs exceeds SQLite INTEGER range")?;

    with_connection(config, |conn| {

View on GitHub (pinned to 7491200858)

Solutions

  1. Derive the provider from the filter itself — add_source(config, filter.provider(), ...) — so the two can never diverge
  2. If the provider is the source of truth, rebuild the filter as the matching variant (FilterSpec::Github for ProviderSlug::Github)
  3. In the frontend, reset the filter form whenever the provider selection changes
  4. Validate the pair before persisting and return a field-level error to the user

Example fix

// before — hardcoded provider can drift from the filter variant
let provider = ProviderSlug::Linear;
add_source(&config, provider, conn_id, name, filter, 300, target, 25)?;

// after — derive the provider from the filter tag
add_source(&config, filter.provider(), conn_id, name, filter.clone(), 300, target, 25)?;
Defensive patterns

Strategy: type-guard

Validate before calling

if filter.provider() != provider {
    anyhow::bail!(
        "filter targets {} but source provider is {} — rebuild the filter for the selected provider",
        filter.provider().as_str(),
        provider.as_str()
    );
}

Type guard

fn filter_matches_source(filter: &FilterSpec, provider: ProviderSlug) -> bool {
    filter.provider() == provider
}

Try / catch

match add_source(&config, provider, conn, name, filter, interval, target, max).await {
    Ok(src) => Ok(src),
    Err(e) if format!("{e:#}").contains("does not match source provider") => {
        // UI sent a stale filter for a different provider — reset the filter form, not a retry
        Err(anyhow::anyhow!("filter/provider mismatch: reset the filter for the selected provider"))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: add_source(config, ProviderSlug::Github, ...) called with FilterSpec::Notion{..} (or any cross pair). Typically happens when the provider is chosen independently in the UI while the filter JSON was deserialized from a stale form or a different tab's picker.

Common situations: Frontend sends {provider:'linear', filter:{provider:'github',...}} after the user switched provider without resetting the filter; copy-pasting a source template and editing only the provider; hand-built RPC payloads.

Related errors


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