v1.0.130
Architectural rollback of v1.0.128's lockfile + locking_mode = EXCLUSIVE in SQLiteBase. The original v1.0.128 fix was an over-correction. The actual root causes of #560 (multi-instance WAL contention reported by @ishabana) were #559 (zombie MCP child accumulation) and #561 (Pi misdetection writing to Claude Code's data dir) — both already fixed. With those root causes closed, normal usage is 1 MCP process per Claude session per project; legitimate multi-window UX is 2 processes on the same DB, which WAL handles natively.
The lockfile broke real users. Anyone opening the same project in 2 Claude Code windows hit "Another context-mode server is already running (PID: XXXX). Stop it before starting a new instance." — silent stderr fail in agent UI. v1.0.130 returns to the v1.0.127 multi-writer default, locked in with regression-proof tests.
What changed
Removed
src/util/db-lock.ts— entire helper (185 LoC) deleted.acquireDbLock,releaseDbLock,DatabaseLockedErrorno longer exist.SQLiteBasector — noacquireDbLockcall, nolocking_mode = EXCLUSIVEpragma, noreleaseDbLockcleanup, no tmpdir skip-gate (no longer needed).- v1.0.128 lockfile-specific tests in
tests/util/db-base-platform-gate.test.ts— replaced with multi-writer + lifecycle suite.
Kept
applyWALPragmas:journal_mode = WAL,synchronous = NORMAL,mmap_size = 268435456(256MB),busy_timeout = 30000(vianew Database(..., { timeout }))closeDB()runswal_checkpoint(TRUNCATE)(already in place since #244)withRetryforSQLITE_BUSY(already in place since #243)- SQLite default
wal_autocheckpoint = 1000— battle-tested - v1.0.128 #559 sibling-MCP kill on
/ctx-upgrade(src/util/sibling-mcp.ts) — zombie process accumulation is a real bug - v1.0.124 #545 algorithmic env-leakage (
foreignWorkspaceEnv) - v1.0.129 #561 identification env scrub (
foreignIdentificationEnv) — Pi/Claude data partitioning - v1.0.126 Algo-D4 boot integrity check + Algo-D5 doctor surface (HealthCheck protocol)
- All 14 other adapters unchanged
Added — regression-proof anchors
Two invariant tests in tests/util/db-base-platform-gate.test.ts:
- Behavioral:
"INVARIANT: two SQLiteBase instances on the same tmpdir path can both open and write (multi-writer default)"— opens two SessionDB instances on same path, writes from both, asserts both succeed. Catches anyone re-introducingacquireDbLockorEXCLUSIVEpragma. - Source-pin:
"INVARIANT: SQLiteBase ctor must NOT contain acquireDbLock or locking_mode=EXCLUSIVE"— readssrc/db-base.tssource, regex-asserts neither symbol appears in theSQLiteBaseclass body. Catches anyone re-adding the constructs.
These two compose into defense-in-depth: future contributors hit either the runtime test failure or the source-pin failure before merge.
ADR — docs/adr/0001-sessiondb-multi-writer.md
System-design decision documented:
- Context: v1.0.128 introduced lockfile, broke multi-window UX. 10 parallel grill verdicts (UX, SQLite Expert, Security, Performance, Architect, Test, SRE, PM, Data, DevEx) — 7 voted REMOVE, 2 voted KEEP, 1 voted ALTER. Net: removal warranted.
- Decision: SessionDB is multi-writer-safe. WAL + busy_timeout + withRetry is the SQLite-native concurrency story. Process-identity invariants (one MCP per project) belong in
src/util/sibling-mcp.ts(process layer), not the DB layer. - Consequences: legitimate multi-window UX restored; root causes (#559 + #561) handle actual concurrency safety; ContentStore parity preserved (was always multi-writer).
- Alternatives considered + rejected: lockfile (over-correction), leader election (over-engineering), EXCLUSIVE pragma (breaks WAL multi-writer contract).
Tests
3236 pass · 8 pre-existing OpenCode baseline failures (unchanged from v1.0.122 baseline). Full test suite verified locally before push (per Mert's directive — CI breakage prevention).
ContentStore concurrency tests (tests/store.test.ts > "concurrent DB access") — 4/4 PASS. v1.0.128 had broken these; v1.0.129 fixed via EXCLUSIVE move; v1.0.130 confirms green via the rollback.
Compatibility
15 adapters / 3 OS. Behaviorally identical to v1.0.127 at the SQLiteBase layer + retains all v1.0.128/129's correct fixes (#559 + #561 + Algo-D4/5 + #545 + #547 etc).
| Adapter | Effect of v1.0.130 |
|---|---|
| Pi | Still gets v1.0.129 #561 identification scrub — data correctly partitioned to ~/.pi/context-mode/
|
| Claude Code | Multi-window users no longer see "Another context-mode server is already running" error
|
| All 15 (universal) | SessionDB returns to multi-writer default. ContentStore unchanged (was always multi-writer). |
Upgrade
npm install -g context-mode@latest
# inside Claude Code:
/ctx-upgrade
# fully restart Claude Code (Cmd+Q + reopen)If you saw "Another context-mode server is already running (PID: XXXX)" after v1.0.128 → upgrade restores multi-window. If you ran multiple Claude Code windows on same project before v1.0.128 → that workflow works again.
Architectural lesson
Process-identity invariants belong in process layer (sibling-mcp), NOT DB layer (lockfile/EXCLUSIVE). WAL +
withRetry(SQLITE_BUSY)is sufficient for multi-writer SQLite. EXCLUSIVE locking is opt-out — never apply it in a base class shared by multi-writer consumers. Lockfile UX (PID error) is an architectural anti-pattern when consumers are legitimately multi-window.
Now codified in docs/adr/0001-sessiondb-multi-writer.md. Future "let's prevent multi-instance" proposals must justify why this ADR's reasoning no longer applies.
Thanks
@ishabana for the original #560 report — without your data on the 5-instance scenario, neither the over-correction (v1.0.128) nor the architectural lesson (v1.0.130) would have been visible. Multi-window UX is restored thanks to the iterative refinement your reports drove.