Summary
Four tool handlers run state machine guards outside the transaction() callback, then perform writes inside a separate transaction. This creates a time-of-check-to-time-of-use (TOCTOU) race where two agents can both pass the guard simultaneously and both write successfully.
Impact
Critical — In multi-agent deployments (the exact threat model the Single-Writer Engine v3 addresses), concurrent agents can:
- Both re-plan the same completed slice/task
- Both reassess the same milestone simultaneously
- Both replan the same blocked slice
SQLite's single-writer lock prevents corruption but does not prevent both operations from succeeding — the second write wins silently.
Affected Files
plan-task.ts
- Line 80:
getSlice() guard — outside txn
- Line 88:
getTask() guard — outside txn
- Line 94:
transaction(() => { insertTask/upsertTaskPlanning }) — separate txn
plan-slice.ts
- Line 149:
getMilestone() guard — outside txn
- Line 157:
getSlice() guard — outside txn
- Line 166:
transaction(() => { ... }) — separate txn
reassess-roadmap.ts
- Line 108:
getMilestone() check — outside txn
- Line 117:
getSlice() for completedSliceId — outside txn
- Lines 126-146:
getMilestoneSlices() + enforcement — outside txn
- Line 158:
transaction() — separate txn
replan-slice.ts
- Line 94:
getSlice() slice-status check — outside txn
- Line 103:
getTask() blocker check — outside txn
- Lines 112-132: completed-task enforcement — outside txn
- Line 138:
transaction() — separate txn
Correct Pattern (for reference)
complete-task.ts:155-208, complete-slice.ts:223-257, reopen-task.ts:60-92, and reopen-slice.ts:59-88 all correctly colocate guards and writes inside a single transaction() callback.
Fix
Move guard checks into the transaction() callback in all four files, following the same pattern as complete-task.ts. Use a guardError variable set inside the transaction, checked after it returns.
Confidence
85% — structural pattern verified across all tool handlers.
Summary
Four tool handlers run state machine guards outside the
transaction()callback, then perform writes inside a separate transaction. This creates a time-of-check-to-time-of-use (TOCTOU) race where two agents can both pass the guard simultaneously and both write successfully.Impact
Critical — In multi-agent deployments (the exact threat model the Single-Writer Engine v3 addresses), concurrent agents can:
SQLite's single-writer lock prevents corruption but does not prevent both operations from succeeding — the second write wins silently.
Affected Files
plan-task.tsgetSlice()guard — outside txngetTask()guard — outside txntransaction(() => { insertTask/upsertTaskPlanning })— separate txnplan-slice.tsgetMilestone()guard — outside txngetSlice()guard — outside txntransaction(() => { ... })— separate txnreassess-roadmap.tsgetMilestone()check — outside txngetSlice()forcompletedSliceId— outside txngetMilestoneSlices()+ enforcement — outside txntransaction()— separate txnreplan-slice.tsgetSlice()slice-status check — outside txngetTask()blocker check — outside txntransaction()— separate txnCorrect Pattern (for reference)
complete-task.ts:155-208,complete-slice.ts:223-257,reopen-task.ts:60-92, andreopen-slice.ts:59-88all correctly colocate guards and writes inside a singletransaction()callback.Fix
Move guard checks into the
transaction()callback in all four files, following the same pattern ascomplete-task.ts. Use aguardErrorvariable set inside the transaction, checked after it returns.Confidence
85% — structural pattern verified across all tool handlers.