{"record":{"id":"3a825384b37bc826","repo":"affaan-m/ECC","slug":"scheduled-task-id-was-not-found-after-insert","errorCode":null,"errorMessage":"Scheduled task {id} was not found after insert","messagePattern":"Scheduled task (.+?) was not found after insert","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/session/store.rs","lineNumber":1537,"sourceCode":"                updated_at\n             ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)\",\n            rusqlite::params![\n                cron_expr,\n                task,\n                agent_type,\n                profile_name,\n                working_dir.display().to_string(),\n                project,\n                task_group,\n                if use_worktree { 1_i64 } else { 0_i64 },\n                next_run_at.to_rfc3339(),\n                now.to_rfc3339(),\n                now.to_rfc3339(),\n            ],\n        )?;\n        let id = self.conn.last_insert_rowid();\n        self.get_scheduled_task(id)?\n            .ok_or_else(|| anyhow::anyhow!(\"Scheduled task {id} was not found after insert\"))\n    }\n\n    pub fn list_scheduled_tasks(&self) -> Result<Vec<ScheduledTask>> {\n        let mut stmt = self.conn.prepare(\n            \"SELECT id, cron_expr, task, agent_type, profile_name, working_dir, project, task_group,\n                    use_worktree, last_run_at, next_run_at, created_at, updated_at\n             FROM scheduled_tasks\n             ORDER BY next_run_at ASC, id ASC\",\n        )?;\n\n        let rows = stmt.query_map([], map_scheduled_task)?;\n        rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)\n    }\n\n    pub fn list_due_scheduled_tasks(\n        &self,\n        now: chrono::DateTime<chrono::Utc>,\n        limit: usize,","sourceCodeStart":1519,"sourceCodeEnd":1555,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/session/store.rs#L1519-L1555","documentation":"Raised by the scheduled-task insertion helper in ecc2/src/session/store.rs:1537 when, immediately after INSERT INTO scheduled_tasks and reading last_insert_rowid, get_scheduled_task(id) returns None. last_insert_rowid is connection-local in rusqlite, so a None read-back indicates the row is not visible to the same connection that just inserted it, or a trigger/INSTEAD OF rule rewrote the insert.","triggerScenarios":"A SQLite trigger on scheduled_tasks redirects the insert into another table; the connection is in WAL mode and the read-back uses a different snapshot (unusual for the same connection); last_insert_rowid returns an id that does not correspond to a scheduled_tasks row because another insert preempted it on a shared connection; a schema where scheduled_tasks is a VIEW with INSTEAD OF triggers.","commonSituations":"A third-party migration added triggers on scheduled_tasks; the rusqlite Connection is shared across threads without serialization and a concurrent insert changed last_insert_rowid; manual schema edits turned scheduled_tasks into a view.","solutions":["Inspect the scheduled_tasks schema (SELECT sql FROM sqlite_master WHERE name='scheduled_tasks') for triggers or view definitions and remove any that intercept the insert.","Ensure the rusqlite Connection used by store is not shared across threads without the mutex/actor serialization; last_insert_rowid must be read on the same connection that did the INSERT with no intervening write.","Run the insert + read-back inside an explicit transaction so no other write can land between them.","If the id derivation is the issue, switch get_scheduled_task to SELECT by rowid explicitly."],"exampleFix":"// before: read-back may miss on a contended connection\nself.conn.execute(\"INSERT INTO scheduled_tasks ...\", params![...])?;\nlet id = self.conn.last_insert_rowid();\nself.get_scheduled_task(id)?.ok_or_else(|| anyhow!(\"not found after insert\"))\n\n// after: hold a transaction so the read-back sees the row\nlet tx = self.conn.transaction()?;\ntx.execute(\"INSERT INTO scheduled_tasks ...\", params![...])?;\nlet id = tx.last_insert_rowid();\nlet task = get_scheduled_task_in_tx(&tx, id)?.ok_or_else(|| anyhow!(\"not found after insert\"))?;\ntx.commit()?;\nOk(task)","handlingStrategy":"validation","validationCode":"// Wrap insert + read-back in a transaction so no other write can land\n// between them, and read back inside the same tx.\npub fn create_scheduled_task_atomic(&self, /* fields */) -> anyhow::Result<ScheduledTask> {\n    let tx = self.conn.transaction()?;\n    tx.execute(\n        \"INSERT INTO scheduled_tasks (...) VALUES (...)\",\n        rusqlite::params![...],\n    )?;\n    let id = tx.last_insert_rowid();\n    let task = get_scheduled_task_in_tx(&tx, id)?\n        .ok_or_else(|| anyhow::anyhow!(\"scheduled task {id} not found after insert\"))?;\n    tx.commit()?;\n    Ok(task)\n}\n\n// Also assert no intercepting triggers exist before relying on this path.\nfn assert_no_scheduled_tasks_triggers(conn: &rusqlite::Connection) -> anyhow::Result<()> {\n    let n: i64 = conn.query_row(\n        \"SELECT COUNT(*) FROM sqlite_master WHERE type='trigger' AND tbl_name='scheduled_tasks'\",\n        [], |r| r.get(0),\n    )?;\n    if n > 0 { anyhow::bail!(\"{n} triggers on scheduled_tasks may intercept inserts\"); }\n    Ok(())\n}","typeGuard":"// No type guard; the issue is connection/transaction usage. The defense is\n// the transaction wrapper and the trigger audit above.","tryCatchPattern":"// Surface read-back failure with the trigger/connection context so the\n// operator knows where to look.\nmatch store.create_scheduled_task(/* ... */) {\n    Ok(task) => Ok(task),\n    Err(e) if e.to_string().contains(\"not found after insert\") => {\n        tracing::error!(\"scheduled task insert/read-back mismatch; check triggers and shared connections\");\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Wrap insert + read-back in a single transaction; read back inside the tx.","Ensure the rusqlite Connection is used by one logical writer at a time (DbWriter actor pattern); last_insert_rowid is connection-local and order-sensitive.","Audit sqlite_master for triggers/views on scheduled_tasks after any schema migration.","Do not share a Connection across threads without serialization."],"tags":["database","sqlite","scheduled-task","insert","last-insert-rowid","transaction"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}