github mksglu/context-mode v1.0.130

one hour ago

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, DatabaseLockedError no longer exist.
  • SQLiteBase ctor — no acquireDbLock call, no locking_mode = EXCLUSIVE pragma, no releaseDbLock cleanup, 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 (via new Database(..., { timeout }))
  • closeDB() runs wal_checkpoint(TRUNCATE) (already in place since #244)
  • withRetry for SQLITE_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:

  1. 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-introducing acquireDbLock or EXCLUSIVE pragma.
  2. Source-pin: "INVARIANT: SQLiteBase ctor must NOT contain acquireDbLock or locking_mode=EXCLUSIVE" — reads src/db-base.ts source, regex-asserts neither symbol appears in the SQLiteBase class 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.

Don't miss a new context-mode release

NewReleases is sending notifications on new releases.