Skip to content

fix(server): wait for concurrent SQLite writers instead of failing with SQLITE_BUSY - #5134

Open
ostapondo wants to merge 1 commit into
pingdotgg:mainfrom
ostapondo:fix/sqlite-busy-timeout
Open

fix(server): wait for concurrent SQLite writers instead of failing with SQLITE_BUSY#5134
ostapondo wants to merge 1 commit into
pingdotgg:mainfrom
ostapondo:fix/sqlite-busy-timeout

Conversation

@ostapondo

@ostapondo ostapondo commented Jul 31, 2026

Copy link
Copy Markdown

Fixes #5099

Concurrent writes to the state database from separate processes — e.g. t3 auth session issue while a server is running, or a few CLI invocations in parallel — intermittently fail with database is locked. The persistence layer never sets busy_timeout, so SQLite gives up on a contended write lock immediately instead of waiting for it to clear.

The shared setup layer now applies PRAGMA busy_timeout = 5000 (the value scripts/t3-sqlite-state.ts already uses) ahead of the WAL/foreign-keys pragmas and migrations, so it covers the server, the CLI, and both the node and bun clients through the one place every writable connection goes through.

Verified:

  • New test reproduces the failure cross-process: a child process holds BEGIN IMMEDIATE on the same file while the layer writes. It fails with database is locked on main and passes with the fix (a second test pins the pragma value). Stable across repeated runs.
  • The repro from the issue: on main, parallel t3 auth session issue runs against one state dir failed intermittently with the exact stack from the issue (AuthSessionRepository.createdatabase is locked); with the fix, 24/24 runs succeeded.

Scope: this only makes writers wait out short-lived lock contention; it does not change WAL or durability behavior.

Written by Claude (Fable 5) in Claude Code, directed and reviewed by @ostapondo.


Open in Devin Review

Note

Low Risk
Single pragma in existing setup path; no WAL or durability changes—only retry behavior on lock contention.

Overview
Fixes intermittent database is locked when the CLI and server (or parallel CLI runs) write the same state DB: the shared SQLite setup layer now runs PRAGMA busy_timeout = 5000 before foreign keys, WAL, and migrations, matching scripts/t3-sqlite-state.ts so every writable connection waits briefly instead of failing immediately.

New tests spawn a child that holds BEGIN IMMEDIATE while the layer inserts, and assert busy_timeout is 5000 on the in-memory persistence layer.

Reviewed by Cursor Bugbot for commit 3cc9673. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix SQLite busy errors by setting PRAGMA busy_timeout to 5000ms

Sets PRAGMA busy_timeout = 5000 in the SQLite connection setup so that statements wait up to 5 seconds for a write lock instead of immediately failing with SQLITE_BUSY. The pragma is applied in Sqlite.ts before foreign key enforcement and WAL/migration setup. Tests in Sqlite.test.ts verify both the wait behavior with a concurrent writer and that the timeout value is correctly applied.

Macroscope summarized 3cc9673.

Summary by CodeRabbit

  • Bug Fixes

    • Improved SQLite handling for concurrent writes.
    • Write operations now wait up to five seconds when the database is temporarily locked, reducing immediate failures during simultaneous CLI and server activity.
  • Tests

    • Added coverage for concurrent write handling and the shared database lock timeout configuration.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SQLite initialization now sets PRAGMA busy_timeout = 5000 before other pragmas. Tests cover concurrent writer handling and verify the timeout value for shared in-memory persistence.

Changes

SQLite busy timeout

Layer / File(s) Summary
Configure SQLite busy timeout
apps/server/src/persistence/Layers/Sqlite.ts
SQLite setup applies a five-second busy timeout before enabling WAL and foreign keys.
Validate concurrent writer behavior
apps/server/src/persistence/Layers/Sqlite.test.ts
Tests hold a write lock in a child process, verify that another insert waits successfully, clean up the temporary database, and assert a 5000 millisecond timeout for shared in-memory persistence.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: juliusmarminge

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds the requested SQLite busy timeout and tests the concurrent writer behavior described in issue #5099.
Out of Scope Changes check ✅ Passed The changes are limited to the shared SQLite setup and focused persistence tests related to issue #5099.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes the main SQLite concurrency fix.
Description check ✅ Passed The description explains the change, motivation, testing, scope, and absence of UI changes, but it does not use the template headings or checklist.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label Jul 31, 2026
@ostapondo

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot added the size:XS 0-9 changed lines (additions + deletions). label Jul 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/persistence/Layers/Sqlite.test.ts`:
- Around line 15-24: Update the lock-holder setup around lockHolderSource so it
does not require node:sqlite when tests run under Bun. Use a Bun-compatible
bun:sqlite helper for Bun, or conditionally run the existing
process.execPath/node:sqlite lock simulation only on Node while preserving the
lock behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dcdb76e1-b225-477a-bbcf-3c56dda9454b

📥 Commits

Reviewing files that changed from the base of the PR and between a041981 and 0b0f6d3.

📒 Files selected for processing (2)
  • apps/server/src/persistence/Layers/Sqlite.test.ts
  • apps/server/src/persistence/Layers/Sqlite.ts

Comment thread apps/server/src/persistence/Layers/Sqlite.test.ts
@macroscopeapp

macroscopeapp Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved 3cc9673

This is a straightforward bug fix adding a standard SQLite busy_timeout pragma to handle concurrent write access from CLI and server processes. The change is minimal, well-documented, and includes tests verifying the behavior.

You can customize Macroscope's approvability policy. Learn more.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 31, 2026
…th SQLITE_BUSY

The CLI and server open the same state database from separate processes,
but no busy_timeout was ever set, so a write that hit another process's
write lock failed immediately with "database is locked" instead of
waiting for the lock to clear.
@ostapondo
ostapondo force-pushed the fix/sqlite-busy-timeout branch from e74059a to 3cc9673 Compare August 2, 2026 19:12
@macroscopeapp
macroscopeapp Bot dismissed their stale review August 2, 2026 19:12

Dismissing prior approval to re-evaluate 3cc9673

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: SQLite busy_timeout is never set — concurrent CLI/server writes fail with "database is locked"

1 participant