Repository navigation
fix(shared): the SQLite reader pool comes back after a failed start - #30818
Conversation
When a reader worker failed to start, the client turned the reader pool off for the rest of the process, so every read ran on the writer connection until a restart. A failed start now turns the pool off with one warning, and a background retry (1 s, doubling to 60 s) brings it back. A database that is not in WAL mode keeps readers off without retrying. A gauge, t3_sqlite_reader_pool_available, shows the state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — SQLite reader workers now recover automatically after transient startup failures, with capped backoff and availability metrics. Existing read-only safeguards and main-connection fallback remain in place, limiting the change to reader availability. You can add or adjust custom eligibility rules. Learn more. |
|
Note Written by Verified in a real dev environmentverified as-is at reproduced on follow-up send, a fresh thread with another real codex response, and history reload passed after recovery. the counts above include only spans with an explicit connection annotation. these are recovery observations, not a performance benchmark. blacksmith passed the 24 sqlite tests and command transcript and gauge observations Not verifiednative ios/android, the electron shell, windows/macos, and remote relay/tunnel connections were not exercised on this linux vm. the running-app fault covered a transient open failure; startup timeout, the 60-second retry cap, permanent non-wal disablement, and closing during retry were not exercised in the app. the existing focused sqlite tests passed. no code changes were needed. |
Closing the client while the reader retry waits, or while a retried reader is starting, leaves no reader starting afterwards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note Written by Claude Opus 5.5 (Claude Code) on behalf of Navid Thanks for checking it in a running app, and for the gauge trace. On the paths you didn't exercise: the start timeout, the 60 s cap and the permanent non-WAL stop each have a focused test (the timeout case of "until a retry starts the reader again", "backs off a reader that never starts without logging each retry", and "keeps readers off without further retries when …"). Closing during a retry had no dedicated test, so I've added one in |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the
Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
…l-recovery Resolve nodeSqliteClient.ts against the move to Effect.undefined: the reader pool's two early returns use it in place of Effect.succeed(undefined) and its diagnostics override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l-recovery Resolve nodeSqliteClient.test.ts against the damaged-database test: keep both sets of imports and both test blocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l-recovery Main rewrote nodeSqliteClient.ts around a shared worker connection for readers and the writer, so the reader pool fix no longer applies as written: take main's version of both files. The following commits re-apply the fix on the new structure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…start A reader that fails to start turns the pool off for the rest of the process. These tests expect a background retry with backoff to bring it back, a gauge that shows the pool's state, no retries for a database that is not in WAL mode, and no retries after the client closes. They fail on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a reader worker failed to start, the reader pool turned off for the rest of the process, so every read ran on the writer until a restart. A failed start now turns the pool off with one warning, and one background retry (1 s, doubling to 60 s) brings it back, with one log line. A database that is not in WAL mode keeps readers off without retrying. The retry belongs to the client and stops when it closes. A gauge, t3_sqlite_reader_pool_available, shows the state. The writer is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every reader-pool client shares one unlabelled gauge, so the OpenCode usage reader overwrites the state database's value. The warn and info log lines already report when the pool goes down and comes back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EvidenceIdentical reproduction on fresh databases: complete a baseline Replay turn; deny the userdata directory and retire readers; confirm reader startup failure and working writer fallback; restore permissions without restarting; send a follow-up turn and reload history three times over 30+ seconds. Linux, Chromium 156, 1280×800, light theme. Panels are explicitly labeled inspector observations, not product UI.
Base served 614 writer operations and zero reader operations after restoration. Head served 172 reader operations and 412 writer operations. Server PID remained unchanged on each side; both follow-up turns completed and survived history reloads. Scenario · Observer · Reproduction setup · Measured observations · Runtime transcript · Recovered proof summary. The comparison parent received SIGTERM before summarizing; the proof summary was rebuilt with the runner's exported helpers from both completed reports. Not shown:
|







Problem
When one SQLite reader worker fails to start (an open error, a database not in WAL mode, or the 10 s start timeout),
nodeSqliteClientturns the whole reader pool off for the rest of the process. Every read then runs on the writer until the server restarts, queued behind writes. A transient failure is enough: onmain, after one failed start no reader starts again (in a test, none in 10 virtual minutes and 5 later reads).Change
ERR_SQLITE_NOT_WAL, so the retry can tell it apart.t3_sqlite_reader_pool_available(0 or 1), shows the state.The change is in
makeReader, the reader pool's wrapper around the shared worker connection; the writer is unchanged. The backoff is fixed in code; there is no setting. One file:packages/shared/src/nodeSqliteClient.ts. Server only; no client or contract change.Scope and approval
This is submitted under the contributing guide's exception for a very small, focused fix to an obvious bug: the defect is shown by tests that fail on
main(below), the product change is +65/−5 lines in one file (the rest is tests), and apart from the availability gauge it adds no behaviour, default or setting.A focused reliability fix for an obvious defect: today a transient start failure leaves readers off for the life of the process. It changes no product default, setting or contract. The only addition beyond the fix is the availability gauge, so the state can be seen. A separate follow-up PR edits the same file; it will be opened after this one.
Background and measurements: #31002 (context, not an approval request).
Verification
Failing first (test clock and queues only; the tests assert events and counts, never elapsed time). The tests were committed alone first and fail on
main@9db3cb109by assertion:vp test run packages/shared/src/nodeSqliteClient.test.ts -t "until a retry starts the reader again": two cases, a reader that crashes at start and one that times out at start. Reads go to the writer, a retry starts the reader again, reads route back to the readers and the gauge goes 1 → 0 → 1. Both fail onmain.-t "backs off a reader that never starts without logging each retry": 12 retries in 10 virtual minutes (1 s doubling to a 60 s cap) and exactly one log line. Fails onmain.-t "keeps readers off without further retries when": not in WAL mode, found at start or by a retry; one warning and no further retries.mainalso leaves readers off in both cases; there they fail only on what this change adds (the gauge, and the retry).-t "stops retrying the reader when the client closes": closing the client while the retry waits, or while a retried reader is starting, leaves no reader starting in the 10 virtual minutes after. Onmain, which never retries, only the second fails; both fail when the retry is made to outlive the client.On
main@9db3cb109, six of the seven cases above fail and the file's other 28 tests pass; with this change, 35/35 pass. Removing any of the three gauge updates, the not-WAL code, the backoff growth, the one-line logging, the retry's context isolation or the retry's tie to the client's scope each fails a test (mutation checks).vp run check:changed: passes (format, lint, knip, typecheck of 35 packages, and 37 tests in the 2 affected test files), on macOS with Node 26.12.1. The server integration suites (vp test run apps/server/integration) also pass, 364 tests in 26 files, and so do the provider replay fixture suites (vp test run apps/server/src/orchestration-v2/testkit), 252 tests in 17 files, and the other tests that start reader workers (apps/server/src/persistenceand the opencode usage reader), 89 passed and 1 skipped.No benchmark: the problem appears only when a reader start fails, which a load benchmark does not exercise.
Not checked
Made with Claude Opus 5.5 via Claude Code. Reviewed by GPT-6.1 Sol via Codex.
🤖 Generated with Claude Code