fix(indexer): guard cleanup rollback so it never masks the real build error
Reported as "cannot rollback - no transaction is active" — a secondary error. SQLite auto-rolls back certain failures (SQLITE_BUSY / SQLITE_BUSY_SNAPSHOT, disk full), after which the per-file loop's unguarded ROLLBACK in its catch threw over the real error and aborted the whole build instead of skipping just the bad file. Stopgap only (see docs/adr/0006 for the planned full fix): - Add safeRollback(db) in both indexers: it swallows only the rollback's own error, so the true cause surfaces. Per-file failures are logged and the build continues; the finalize failure still propagates. - Give the skill's node:sqlite connection an explicit PRAGMA busy_timeout=5000 (it has no default). The app adds none: better-sqlite3 already defaults to 5000ms, so busy_timeout is NOT the root-cause fix and is not treated as one. Add tests/app-rollback-guard.test.mjs: injects a DB that faithfully reproduces "a write auto-rolls back the txn, then ROLLBACK errors" and asserts the build survives (bad file skipped, other file indexed). Revert-checked: without the guard the test fails with the exact "cannot rollback - no transaction is active". docs/adr/0006 records the real fix (shared runWriteTransaction, single-writer coordination, BEGIN IMMEDIATE, whole-transaction retry, PASSIVE checkpointing) as deferred, two-phase work — and why bumping busy_timeout is not it. Verified: suite 124/124, typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e44ab7a1da
commit
44029676d4
+11
-2
@@ -9,6 +9,15 @@ import { parse as codexParse } from './providers/codex.ts';
|
||||
|
||||
const HISTORY_PATH = path.join(CLAUDE_DIR, 'history.jsonl');
|
||||
|
||||
// A rollback inside a catch must never throw over the real error. SQLite
|
||||
// auto-rolls back certain failures (SQLITE_BUSY, disk full, ...); a following
|
||||
// explicit ROLLBACK then throws "cannot rollback - no transaction is active",
|
||||
// which would both mask the true cause and turn a skippable per-file error into
|
||||
// a whole-build failure. Swallow only the rollback's own error.
|
||||
function safeRollback(db) {
|
||||
try { db.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
||||
}
|
||||
|
||||
|
||||
function needsReindex(db, fp) {
|
||||
const mt = fs.statSync(fp).mtimeMs;
|
||||
@@ -193,7 +202,7 @@ function buildIndex({ force = false } = {}) {
|
||||
}
|
||||
db.exec('COMMIT');
|
||||
} catch (e) {
|
||||
db.exec('ROLLBACK');
|
||||
safeRollback(db);
|
||||
process.stderr.write(`Warning: failed to index ${f.path}: ${e.message}\n`);
|
||||
}
|
||||
}
|
||||
@@ -208,7 +217,7 @@ function buildIndex({ force = false } = {}) {
|
||||
db.prepare("INSERT OR REPLACE INTO index_state (jsonl_path, mtime, lines_processed) VALUES ('__last_build__', ?, 0)").run(Date.now());
|
||||
db.exec('COMMIT');
|
||||
} catch (e) {
|
||||
db.exec('ROLLBACK');
|
||||
safeRollback(db);
|
||||
process.stderr.write(`Warning: failed to finalize index: ${e.message}\n`);
|
||||
}
|
||||
db.close();
|
||||
|
||||
Reference in New Issue
Block a user