fix(core): move liveTaskIds query inside transaction for atomicity

Addresses CodeRabbit TOCTOU concern: querying live task IDs inside the
transaction ensures the check and inserts are atomic under SQLite locking,
preventing FK violations from concurrent deletes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Timothy Laurent
2026-05-04 10:33:11 -07:00
parent 3a71f133f9
commit 8dee02fbe9

View File

@@ -4744,21 +4744,21 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
let validEntries = batch; let validEntries = batch;
let flushSucceeded = false; let flushSucceeded = false;
try { try {
// Filter out entries for deleted tasks to prevent FK violations this.db.transaction(() => {
// from poisoning the entire buffer. // Query live task IDs inside the transaction so the check is
const liveTaskIds = new Set( // atomic with the inserts (prevents TOCTOU FK violations).
(this.db.prepare("SELECT id FROM tasks").all() as Array<{ id: string }>).map((r) => r.id), const liveTaskIds = new Set(
); (this.db.prepare("SELECT id FROM tasks").all() as Array<{ id: string }>).map((r) => r.id),
validEntries = batch.filter((e) => liveTaskIds.has(e.taskId));
const dropped = batch.length - validEntries.length;
if (dropped > 0) {
console.warn(
`[fusion] Dropped ${dropped} buffered agent log entries for deleted tasks (${this.db.path})`,
); );
} validEntries = batch.filter((e) => liveTaskIds.has(e.taskId));
const dropped = batch.length - validEntries.length;
if (dropped > 0) {
console.warn(
`[fusion] Dropped ${dropped} buffered agent log entries for deleted tasks (${this.db.path})`,
);
}
if (validEntries.length > 0) { if (validEntries.length > 0) {
this.db.transaction(() => {
const stmt = this.db.prepare(` const stmt = this.db.prepare(`
INSERT INTO agentLogEntries (taskId, timestamp, text, type, detail, agent) INSERT INTO agentLogEntries (taskId, timestamp, text, type, detail, agent)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
@@ -4767,8 +4767,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
stmt.run(entry.taskId, entry.timestamp, entry.text, entry.type, entry.detail, entry.agent); stmt.run(entry.taskId, entry.timestamp, entry.text, entry.type, entry.detail, entry.agent);
} }
this.db.bumpLastModified(); this.db.bumpLastModified();
}); }
} });
flushSucceeded = true; flushSucceeded = true;
} finally { } finally {
// Always drain the original slice from the buffer. // Always drain the original slice from the buffer.