{"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":null,"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/8321021c54d670126ce3b2969d5deb880b4b0c2a/ecc2/src/session/store.rs#L1519-L1555","documentation":"After INSERTing a scheduled task, the store reads last_insert_rowid() and immediately re-fetches the row via get_scheduled_task. If that read returns None, the row written moments earlier cannot be read back, so the store raises `Scheduled task {id} was not found after insert`. It is a post-insert readback invariant check.","triggerScenarios":"INSERT succeeds, last_insert_rowid() returns an id, but get_scheduled_task(id) finds no row — e.g. another connection deleted the row between insert and read, last_insert_rowid() returning a stale id from a different table because the INSERT silently failed to insert, or a trigger/constraint anomaly.","commonSituations":"Concurrent cleanup jobs deleting scheduled tasks; sharing one connection where last_insert_rowid reflects another statement; SQLite WAL contention; misuse of the same conn from multiple threads.","solutions":["Check for concurrent deletions of scheduled tasks between insert and readback.","Ensure all statements on the connection use the same thread/serialized access so last_insert_rowid is valid.","Wrap insert + readback in a transaction to make the invariant atomic.","Enable SQLite foreign-key/trigger logging to see if a trigger removed the row."],"exampleFix":"// before\nlet id = self.conn.last_insert_rowid();\nself.get_scheduled_task(id)?.ok_or_else(|| anyhow::anyhow!(\"Scheduled task {id} was not found after insert\"))\n// after\nself.conn.execute(\"BEGIN IMMEDIATE\", [])?;\n// ... insert ...\nlet id = self.conn.last_insert_rowid();\nlet task = self.get_scheduled_task(id)?;\nself.conn.execute(\"COMMIT\", [])?;\ntask.ok_or_else(|| anyhow::anyhow!(\"Scheduled task {id} was not found after insert\"))","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"was not found after insert\") => {\n        // rowid race: retry the insert once inside a transaction\n        retry_in_transaction(|| insert_and_fetch())?;\n    }\n    Err(e) => return Err(e),\n    Ok(task) => {},\n}","preventionTips":["Always wrap INSERT + readback in a single transaction.","Never share one SQLite connection across threads without serialization.","Avoid relying on last_insert_rowid after conditional statements; capture the rowid immediately after the INSERT.","Audit for background jobs that delete scheduled tasks aggressively."],"tags":["sqlite","invariant","scheduled-task","database"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}