tinyhumansai/openhuman · error · anyhow::Error
patch filter provider '{}' does not match source provider '{
Error message
patch filter provider '{}' does not match source provider '{}' What it means
Thrown by `task_sources::store::update_source` when a `TaskSourcePatch` carries a filter whose provider slug (`filter.provider()`) differs from the stored source's `provider`. A task source is bound to exactly one provider (GitHub, Notion, ...), so its filter payload must stay provider-tagged consistently. The check runs before any write, so the row is left untouched.
Source
Thrown at src/openhuman/integrations/task_sources/store.rs:161
///
/// **Implementation note:** this function opens three separate SQLite
/// connections (read-modify-write + read-back). At settings-panel scale the
/// overhead is acceptable, but there is a theoretical TOCTOU window between
/// the initial `get_source` and the subsequent `UPDATE`. A future refactor
/// could fold all three operations into a single `with_connection` call using
/// a SQL `UPDATE … RETURNING` pattern.
pub fn update_source(config: &Config, id: &str, patch: TaskSourcePatch) -> Result<TaskSource> {
let mut source = get_source(config, id)?;
if let Some(name) = patch.name {
source.name = Some(name).filter(|s| !s.trim().is_empty());
}
if let Some(enabled) = patch.enabled {
source.enabled = enabled;
}
if let Some(filter) = patch.filter {
if filter.provider() != source.provider {
anyhow::bail!(
"patch filter provider '{}' does not match source provider '{}'",
filter.provider().as_str(),
source.provider.as_str()
);
}
source.filter = filter;
}
if let Some(interval_secs) = patch.interval_secs {
source.interval_secs = interval_secs;
}
if let Some(target) = patch.target {
source.target = target;
}
if let Some(max) = patch.max_tasks_per_fetch {
source.max_tasks_per_fetch = max;
}
if let Some(connection_id) = patch.connection_id {
source.connection_id = Some(connection_id).filter(|s| !s.trim().is_empty());View on GitHub (pinned to 7491200858)
Solutions
- Send the patch filter whose provider matches `source.provider` — fetch the source first and mirror its provider in the new filter.
- Omit `filter` from the patch entirely when only renaming, toggling `enabled`, `interval_secs`, or `target`.
- In the UI, reset the filter object whenever the provider selector changes instead of mutating it in place.
- If you genuinely need a different provider, create a new task source rather than patching the old one.
Example fix
// before — patching a github source with a notion filter
let patch = TaskSourcePatch { filter: Some(notion_filter), ..Default::default() };
store::update_source(&config, "src-1", patch)?;
// after — keep provider, or send no filter
let current = store::get_source(&config, "src-1")?;
let patch = TaskSourcePatch {
name: Some("renamed".into()),
filter: None, // unchanged; only patch what you mean to change
..Default::default()
};
store::update_source(&config, "src-1", patch)?; Defensive patterns
Strategy: validation
Validate before calling
let current = task_sources::store::get_source(&config, id)?;
if let Some(filter) = &patch.filter {
anyhow::ensure!(
filter.provider() == current.provider,
"filter provider {} != source provider {}",
filter.provider().as_str(), current.provider.as_str()
);
} Prevention
- Only include fields you intend to change in a patch; leave `filter` as None when untouched.
- Reset the filter object in edit forms whenever the provider selection changes.
- Never copy a filter payload between sources of different providers.
When it happens
Trigger: `config.update_task_source` / `update_source(config, id, patch)` where `patch.filter` was serialized from a different provider's filter spec than the source was created with — e.g. patching a GitHub source with a `notion`-tagged filter, or a client that sends the whole filter object from a different source row as 'unchanged' filler.
Common situations: Frontend edit form reusing the filter object of the previously selected provider after the user switched providers; copy-pasting a patch payload between sources of different providers; API clients that always send `filter` even when unchanged, with a default/hardcoded value.
Related errors
- filter provider '{}' does not match source provider '{}'
- mascot manifest: missing mascots array
- mascot manifest: no renderable mascots
- audio blob is empty
- audio blob is empty
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/1f35e56b86a56daf.
Report an issue: GitHub.