affaan-m/ECC · error · anyhow::Error
Remote dispatch request {id} was not found after insert
Error message
Remote dispatch request {id} was not found after insert What it means
Raised by the remote-dispatch-request insertion helper in ecc2/src/session/store.rs:1670 when get_remote_dispatch_request(id) returns None right after INSERT INTO ... and last_insert_rowid. Same class as the scheduled-task read-back failure: the row is not visible to the inserting connection, typically due to triggers, a shared connection, or the table being a view.
Source
Thrown at ecc2/src/session/store.rs:1670
target_session_id,
task,
target_url,
task_priority_db_value(priority),
agent_type,
profile_name,
working_dir.display().to_string(),
project,
task_group,
if use_worktree { 1_i64 } else { 0_i64 },
source,
requester,
now.to_rfc3339(),
now.to_rfc3339(),
],
)?;
let id = self.conn.last_insert_rowid();
self.get_remote_dispatch_request(id)?.ok_or_else(|| {
anyhow::anyhow!("Remote dispatch request {id} was not found after insert")
})
}
pub fn list_remote_dispatch_requests(
&self,
include_processed: bool,
limit: usize,
) -> Result<Vec<RemoteDispatchRequest>> {
let sql = if include_processed {
"SELECT id, request_kind, target_session_id, task, target_url, priority, agent_type, profile_name, working_dir,
project, task_group, use_worktree, source, requester, status,
result_session_id, result_action, error, created_at, updated_at, dispatched_at
FROM remote_dispatch_requests
ORDER BY CASE status WHEN 'pending' THEN 0 WHEN 'failed' THEN 1 ELSE 2 END ASC,
priority DESC, created_at ASC, id ASC
LIMIT ?1"
} else {
"SELECT id, request_kind, target_session_id, task, target_url, priority, agent_type, profile_name, working_dir,View on GitHub (pinned to 01e15490f0)
Solutions
- Check SELECT sql FROM sqlite_master WHERE type='trigger' AND tbl_name LIKE '%dispatch%' for intercepting triggers.
- Wrap insert + read-back in a transaction and read back within the same tx so no other write can interfere.
- Guarantee the connection is used by one logical writer at a time (the DbWriter actor pattern); last_insert_rowid is only meaningful before any other write on that connection.
- SELECT by rowid in get_remote_dispatch_request to rule out id-derivation bugs.
Example fix
// before
self.conn.execute("INSERT INTO ... dispatch ...", params![...])?;
let id = self.conn.last_insert_rowid();
self.get_remote_dispatch_request(id)?.ok_or_else(|| anyhow!("not found after insert"))
// after: atomic insert + read-back in one tx
let tx = self.conn.transaction()?;
tx.execute("INSERT INTO ... dispatch ...", params![...])?;
let id = tx.last_insert_rowid();
let req = get_remote_dispatch_request_in_tx(&tx, id)?.ok_or_else(|| anyhow!("not found after insert"))?;
tx.commit()?;
Ok(req) Defensive patterns
Strategy: validation
Validate before calling
// Same transactional defense as errorIndex 710, applied to dispatch requests.
pub fn create_remote_dispatch_request_atomic(&self, /* fields */) -> anyhow::Result<RemoteDispatchRequest> {
let tx = self.conn.transaction()?;
tx.execute(
"INSERT INTO remote_dispatch_requests (...) VALUES (...)",
rusqlite::params![...],
)?;
let id = tx.last_insert_rowid();
let req = get_remote_dispatch_request_in_tx(&tx, id)?
.ok_or_else(|| anyhow::anyhow!("dispatch request {id} not found after insert"))?;
tx.commit()?;
Ok(req)
}
fn assert_no_dispatch_triggers(conn: &rusqlite::Connection) -> anyhow::Result<()> {
let n: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='trigger' AND tbl_name LIKE '%dispatch%'",
[], |r| r.get(0),
)?;
if n > 0 { anyhow::bail!("{n} triggers on dispatch tables may intercept inserts"); }
Ok(())
} Type guard
// No type guard; the issue is connection/transaction usage. The defense is // the transaction wrapper and the trigger audit above.
Try / catch
match store.create_remote_dispatch_request(/* ... */) {
Ok(req) => Ok(req),
Err(e) if e.to_string().contains("not found after insert") => {
tracing::error!("dispatch insert/read-back mismatch; check triggers and shared connections");
Err(e)
}
Err(e) => Err(e),
} Prevention
- Wrap insert + read-back in a transaction; read back inside the tx.
- Keep the Connection single-writer; last_insert_rowid is only valid before any other write on that connection.
- Audit sqlite_master for triggers/views on the dispatch tables after migrations.
- Do not share a Connection across threads without serialization.
When it happens
Trigger: A trigger or INSTEAD OF rule on the dispatch-requests table swallows the insert; last_insert_rowid returns an id from a different table because a concurrent insert on the same shared connection landed between the INSERT and the read of last_insert_rowid; the table was converted to a view by a migration.
Common situations: Cross-thread use of a single rusqlite Connection without the actor mutex; a custom migration added an AFTER INSERT trigger that moves rows; schema drift after a partial upgrade.
Related errors
- Scheduled task {id} was not found after insert
- candidate alias integrity verification failed
- Session not found: {session_id}
- Missing value for --db
- Work item not found: ${id}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/53df927a45e98afe.
Report an issue: GitHub.