fix(db): retry transient SQLITE_IOERR instead of failing the caller - #1030
Open
halindrome wants to merge 1 commit into
Open
fix(db): retry transient SQLITE_IOERR instead of failing the caller#1030halindrome wants to merge 1 commit into
halindrome wants to merge 1 commit into
Conversation
withRetry only treated SQLITE_BUSY / "database is locked" as retryable, rethrowing everything else on the first attempt. SQLITE_IOERR therefore fell into a gap: no retries here, and isSQLiteCorruptionError() does not match it either, so no recovery ran. A single transient I/O blip — fs pressure, Spotlight/iCloud touching the DB mid-write, a WAL hiccup — hard-failed the write, e.g. ctx_fetch_and_index aborting a page index with "disk I/O error". Add SQLITE_IOERR / "disk I/O error" to a named TRANSIENT_ERROR_PATTERNS list so they get the same 3-attempt backoff as BUSY. Deliberately NOT added to isSQLiteCorruptionError(): that path renames or deletes the DB file, which would destroy a healthy knowledge base over a momentary stall. Retry here; recover there. Also derive the exhaustion message's error class from the actual failure rather than hardcoding SQLITE_BUSY — reporting "database is locked" for an I/O failure sends the caller after the wrong root cause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rm5HmMLjj3ZjvA1iRkjZRs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
withRetryinsrc/db-base.tsonly treatsSQLITE_BUSY/database is lockedas retryable and rethrows everything else on the first attempt.SQLITE_IOERRfalls into a gap:isSQLiteCorruptionError()does not match it either, so no recovery path runs.A single transient I/O blip — fs pressure, Spotlight/iCloud touching the DB mid-write, a WAL hiccup — therefore hard-fails the write. Observed in the field as
ctx_fetch_and_indexaborting a page index withdisk I/O error, after which the session stops using context-mode tools entirely. The DB itself is healthy (PRAGMA integrity_checkreturnsok); only the one write failed.Change
SQLITE_IOERR/disk I/O errorto a namedTRANSIENT_ERROR_PATTERNSlist so they get the same 3-attempt exponential backoff asSQLITE_BUSY.SQLITE_BUSY— reporting "database is locked" for an I/O failure sends the caller after the wrong root cause.Deliberately NOT added to
isSQLiteCorruptionError(): that path renames or deletes the DB file, which would destroy a healthy knowledge base over a momentary stall. Retry here; recover there.Tests
tests/store.test.tsgains 50 lines covering:SQLITE_IOERRretried and succeeding on a later attempt, retry exhaustion reporting the I/O error class rather thanSQLITE_BUSY, and non-transient errors still rethrowing on the first attempt.Verification
npm run typecheck— cleannpm test— 4715 passed, 28 skipped, 3 failedtests/store.test.ts— 127/127 passThe 3 failures (
tests/session-hooks-smoke.test.ts×2,tests/hooks/claude-stop.test.ts×1) reproduce identically on unmodifiednexton this machine, so they are pre-existing and environmental, not introduced here.Bundles are left untouched —
.github/workflows/bundle.ymlregenerates and commits them.