You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: SQLite busy_timeout is never set — concurrent CLI/server writes fail with "database is locked" #5099
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
Run the server as a service (t3 serve, headless deployment).
While the server is active, issue headless sessions from the CLI, e.g. in a loop or a few in parallel: t3 auth session issue --ttl 5m --label test --subject test --json
Intermittently the CLI fails immediately with ServerAuthSessionTokenIssueError whose root cause is database is locked.
Expected behavior
The second writer waits briefly for the lock to clear (standard SQLite busy_timeout behavior) instead of failing instantly.
Actual behavior
ServerAuthSessionTokenIssueError: Failed to issue session token.
[cause]: SessionCredentialIssueError: Failed to issue session credential.
[cause]: PersistenceSqlError: SQL error in AuthSessionRepository.create:query
[cause]: effect/sql/SqlError: Failed to execute statement
[cause]: effect/sql/SqlError/UnknownError: Failed to execute statement
[cause]: Error: database is locked
Root cause
The persistence setup enables WAL but never sets a busy timeout, and the connection is opened without one, so it stays at SQLite's default of 0 ms — any writer-writer collision between the server process and a CLI process on the shared userdata/state.sqlite fails immediately with SQLITE_BUSY.
Setup layer (dist/bin.mjs in v0.0.29, near the migrations bootstrap):
Add one pragma to the shared setup layer so every connection (server and CLI) gets it:
yield*sql`PRAGMA busy_timeout = 5000;`;
Verified locally on v0.0.29: with the pragma added, 6 parallel t3 auth session issue calls against a live server succeed 6/6; without it they intermittently fail with database is locked.
This is likely also the root cause behind #4818 (PersistenceSqlError on OrchestrationCommandReceiptRepository.upsert with the cause hidden — PR #4837 will surface it).
Impact
Intermittent failure of any CLI write while the server is running; affects headless/orchestration workflows that issue sessions programmatically.
Version or commit
0.0.29
Environment
Linux (Debian), Node 22.22.1, t3 serve under systemd --user
Workaround
Retry with backoff around CLI calls, or patch the installed bundle with the pragma above.
Confirming this with independent quantified reproduction on t3 0.0.30, macOS (Darwin 25.6.0), node:sqlite backend, live t3 server running:
Sequential t3 auth session issue --ttl 1m --json: 0/60 failures.
8-way parallel issuance: 2/48 failures; 16-way: ~3–5% failure rate, all with the exact AuthSessionRepository.create → database is locked chain quoted above.
Store state at failure time: PRAGMA journal_mode = wal, PRAGMA busy_timeout = 0 — so any write colliding with the server's writer fails instantly rather than waiting.
Holding an external BEGIN IMMEDIATE on state.sqlite for ~600ms while issuing reproduces it deterministically; a busy_timeout of even a few hundred ms would absorb every collision we observed.
Downstream impact is real for headless/agent orchestration: every CLI verb that issues a session first becomes a ~3–5%-flaky single point of failure under normal server activity. We've worked around it caller-side with a bounded classified retry (JorgeMenaDev/matias@111822a8), but the durable fix is busy_timeout (~3000ms) on the SQLite connection at open, per this issue's recommendation.
Activity
JorgeMenaDev commentedon Aug 4, 2026
Confirming this with independent quantified reproduction on t3 0.0.30, macOS (Darwin 25.6.0), node:sqlite backend, live
t3server running:t3 auth session issue --ttl 1m --json: 0/60 failures.AuthSessionRepository.create→database is lockedchain quoted above.PRAGMA journal_mode=wal,PRAGMA busy_timeout=0— so any write colliding with the server's writer fails instantly rather than waiting.BEGIN IMMEDIATEonstate.sqlitefor ~600ms while issuing reproduces it deterministically; abusy_timeoutof even a few hundred ms would absorb every collision we observed.Two notes that may widen the priority:
OrchestrationCommandReceiptRepository.upsert), consistent with the connection-level pragma being unset everywhere rather than one repository misbehaving.busy_timeout(~3000ms) on the SQLite connection at open, per this issue's recommendation.