Root Cause
configure_pragmas() in src/store/store.c sets SQLite pragmas in the following order:
// Current (buggy) order:
exec_sql(s, "PRAGMA journal_mode = WAL;");
exec_sql(s, "PRAGMA synchronous = NORMAL;");
exec_sql(s, "PRAGMA busy_timeout = 10000;");
sqlite3_open_v2 returns a live connection handle with SQLite's default zero busy timeout. configure_pragmas runs after open_v2 returns. Any lock contention in the window between open_v2 and the PRAGMA busy_timeout execution causes an immediate SQLITE_BUSY failure — no retry, no wait.
Additionally, PRAGMA journal_mode = WAL itself must acquire an exclusive lock to perform the WAL mode transition. With busy_timeout not yet set at that point, a concurrent reader holding a shared lock causes the WAL transition to fail immediately.
Reproduction Scenario
Two processes accessing the same project database simultaneously:
- MCP server running (holds shared read lock on the project
.db via an open store handle)
- CLI invocation:
codebase-memory-mcp cli search_graph '{"repo_path":"..."}'
The CLI calls store_open_internal → sqlite3_open_v2 → configure_pragmas. When configure_pragmas reaches PRAGMA journal_mode = WAL, the connection has zero timeout. If the MCP server holds a lock at that instant, the WAL pragma fails with SQLITE_BUSY immediately instead of waiting 10 seconds.
Fix
Reorder the pragmas so busy_timeout is set first:
// Fixed order:
exec_sql(s, "PRAGMA busy_timeout = 10000;");
exec_sql(s, "PRAGMA journal_mode = WAL;");
exec_sql(s, "PRAGMA synchronous = NORMAL;");
This ensures the connection has a 10-second retry window before journal_mode=WAL attempts to acquire its exclusive lock, and closes the open-to-first-pragma window entirely.
Relationship to Prior Issues
A PR with the fix is attached to this issue.
Root Cause
configure_pragmas()insrc/store/store.csets SQLite pragmas in the following order:sqlite3_open_v2returns a live connection handle with SQLite's default zero busy timeout.configure_pragmasruns afteropen_v2returns. Any lock contention in the window betweenopen_v2and thePRAGMA busy_timeoutexecution causes an immediateSQLITE_BUSYfailure — no retry, no wait.Additionally,
PRAGMA journal_mode = WALitself must acquire an exclusive lock to perform the WAL mode transition. Withbusy_timeoutnot yet set at that point, a concurrent reader holding a shared lock causes the WAL transition to fail immediately.Reproduction Scenario
Two processes accessing the same project database simultaneously:
.dbvia an open store handle)codebase-memory-mcp cli search_graph '{"repo_path":"..."}'The CLI calls
store_open_internal→sqlite3_open_v2→configure_pragmas. Whenconfigure_pragmasreachesPRAGMA journal_mode = WAL, the connection has zero timeout. If the MCP server holds a lock at that instant, the WAL pragma fails withSQLITE_BUSYimmediately instead of waiting 10 seconds.Fix
Reorder the pragmas so
busy_timeoutis set first:This ensures the connection has a 10-second retry window before
journal_mode=WALattempts to acquire its exclusive lock, and closes the open-to-first-pragma window entirely.Relationship to Prior Issues
busy_timeoutfrom 5s to 10s — it did not reorder pragmas. The C rewrite eliminated the ref-counting race but the ordering hazard was not addressed.A PR with the fix is attached to this issue.