{"record":{"id":"eaae06a997f183a7","repo":"tinyhumansai/openhuman","slug":"task-source-id-not-found-eaae06","errorCode":null,"errorMessage":"Task source '{id}' not found.","messagePattern":"Task source '(.+?)' not found\\.","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"src/openhuman/integrations/task_sources/store.rs","lineNumber":223,"sourceCode":"                i64::from(source.max_tasks_per_fetch),\n                source.assigned_executor,\n                id,\n            ],\n        )\n        .context(\"Failed to update task source\")?;\n        Ok(())\n    })?;\n\n    get_source(config, id)\n}\n\npub fn remove_source(config: &Config, id: &str) -> Result<()> {\n    let changed = with_connection(config, |conn| {\n        conn.execute(\"DELETE FROM task_sources WHERE id = ?1\", params![id])\n            .context(\"Failed to delete task source\")\n    })?;\n    if changed == 0 {\n        anyhow::bail!(\"Task source '{id}' not found\");\n    }\n    Ok(())\n}\n\n/// Update a source's `last_fetch_at` / `last_status` after a fetch pass.\npub fn record_fetch(\n    config: &Config,\n    id: &str,\n    finished_at: DateTime<Utc>,\n    reason: FetchReason,\n    status: &str,\n) -> Result<()> {\n    let line = format!(\"{}: {status}\", reason.as_str());\n    with_connection(config, |conn| {\n        conn.execute(\n            \"UPDATE task_sources SET last_fetch_at = ?1, last_status = ?2 WHERE id = ?3\",\n            params![finished_at.to_rfc3339(), line, id],\n        )","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/task_sources/store.rs#L205-L241","documentation":"Thrown by `task_sources::store::remove_source` when `DELETE FROM task_sources WHERE id = ?1` reports zero changed rows — the id was not present to begin with. It is a guard so callers can distinguish 'deleted' from 'nothing was there'. The `Failed to delete task source` context on the SQL error itself is a different, lower-level failure; this message means the DELETE executed fine but matched nothing.","triggerScenarios":"`remove_source(config, id)` with an already-deleted id, a double-submit of a delete RPC, or concurrent deletion (two actors racing to remove the same source).","commonSituations":"Idempotent-looking retry UIs that re-send the delete after a timeout when the first attempt actually succeeded; UI list state gone stale; cleanup loops that iterate ids collected before another process deleted them.","solutions":["Treat 'not found' on delete as success if your operation is idempotent — catch and match the message.","Refresh source ids via `list_sources` before showing/issuing delete actions.","Guard double-submits in the UI (disable the delete button after first click).","If the delete should always match, audit for another actor deleting concurrently."],"exampleFix":"// before\nstore::remove_source(&config, &id)?;\n\n// after — idempotent delete\nmatch store::remove_source(&config, &id) {\n    Ok(()) => Ok(()),\n    Err(e) if e.to_string().contains(\"not found\") => Ok(()), // already gone\n    Err(e) => Err(e),\n}","handlingStrategy":"try-catch","validationCode":"let still_there = task_sources::store::list_sources(&config)?\n    .iter().any(|s| s.id == id);\nif !still_there { /* already deleted; nothing to do */ }","typeGuard":null,"tryCatchPattern":"match task_sources::store::remove_source(&config, &id) {\n    Ok(()) => Ok(()),\n    Err(e) if e.to_string().contains(\"not found\") => Ok(()), // idempotent delete\n    Err(e) => Err(e),\n}","preventionTips":["Make delete flows idempotent: swallow 'not found' as success.","Disable delete buttons after the first click to avoid double-submits.","Reconcile UI lists with list_sources after deletions."],"tags":["task-sources","sqlite","not-found","idempotency"],"backgroundTag":"record-not-found","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}