Skip to content

Repair-free app.NewClient: per-session MCP clients and read-only CLI stop write-bursting the live store#150

Merged
MaxGhenis merged 1 commit into
mainfrom
claude/competent-vaughan-959e2b
Jul 21, 2026
Merged

Repair-free app.NewClient: per-session MCP clients and read-only CLI stop write-bursting the live store#150
MaxGhenis merged 1 commit into
mainfrom
claude/competent-vaughan-959e2b

Conversation

@MaxGhenis

Copy link
Copy Markdown
Owner

Follow-up to #149 (now merged), addressing its adversarial review's non-blocking nit: runServeMCPClient opened the store via app.New, which runs the full startup repair-sweep suite — and MCP hosts spawn one client process per Claude session, so the daemon's live messages.db absorbed N concurrent repair-sweep write bursts at every session start (25 concurrent clients observed live on 2026-07-21).

What changed

  • app.NewClient (internal/app/app.go): repair-free twin of app.New — identical data-dir resolution, permission hardening, and store open (schema migration included), but skips RepairLegacyArtifacts, RepairContentlessRecency, RepairTapbacks, RepairEmptyStubMessages, and the WhatsApp media-placeholder repair. app.New delegates to the same newApp with sweeps on; the sweep block moved verbatim into repairStartupArtifacts. Daemon behavior is unchanged — sweeps still run once per daemon startup.
  • runServeMCPClient now opens via NewClient (the per-session MCP client shape).
  • openCommandReadSource's legacy branch now opens via NewClient, so the advertised read-only CLI commands (read, status) genuinely stop writing to the store they share with the running app.
  • Docs: CLAUDE.md + docs/agent-runbook.md note the repair-free client contract.

Tests

  • TestNewClientPerformsNoStoreWrites (internal/app): seeds one canonical trigger row per sweep, snapshots every messages/conversations row (all columns) plus row counts of all other non-FTS tables through a read-only connection, asserts NewClient leaves the store identical at the row level — then a control leg proves app.New repairs every seeded row, so the no-writes assertion cannot pass vacuously. Mutation-tested: wiring NewClient to run sweeps fails the test.
  • TestRunServeMCPClientDoesNotRepairStore (cmd): runs the real RunServe --mcp-stdio shape to stdin EOF and asserts the trigger row survives, with an app.New contrast leg (called directly — a full daemon RunServe keeps background goroutines holding the store briefly past return, which races the verification open).
  • TestOpenCommandReadSourceLegacyDoesNotRepairStore (cmd): pins the read-only CLI contract.

GOWORK=off go test ./cmd ./internal/app ./internal/tools -count=1 green on the rebase; full go test ./... green; the three new tests green under -race -count=3.

🤖 Generated with Claude Code

MCP hosts spawn one serve --mcp-stdio process per Claude session, and PR
#149's runServeMCPClient opened the store via app.New — which runs the
full startup repair-sweep suite (RepairLegacyArtifacts,
RepairContentlessRecency, RepairTapbacks, RepairEmptyStubMessages, and
the WhatsApp media-placeholder repair) on every open. With dozens of
sessions open (25 observed 2026-07-21), the daemon's live messages.db
absorbed that many concurrent repair-sweep write bursts at every session
start. The adversarial review of #149 flagged this as a follow-up.

app.New now delegates to newApp(logger, true) and keeps its behavior
byte-for-byte (sweeps then demo seed, same error handling); the sweep
block moves verbatim into repairStartupArtifacts. app.NewClient is the
repair-free twin: same data-dir resolution, permission hardening, and
store open (schema migration included), zero repair writes. Used by:

- runServeMCPClient (the per-session MCP client shape)
- openCommandReadSource's legacy branch, so the advertised read-only
  CLI commands (read, status) stop writing to the shared live store

Tests:
- TestNewClientPerformsNoStoreWrites (internal/app) seeds one canonical
  trigger row per sweep, snapshots every messages/conversations row plus
  all other table counts over a read-only connection, and asserts
  NewClient leaves the store identical; a control leg then proves
  app.New repairs every seeded row, so the assertion cannot pass
  vacuously (mutation-tested: NewClient wired to run sweeps fails).
- TestRunServeMCPClientDoesNotRepairStore (cmd) runs the real
  RunServe --mcp-stdio shape to stdin EOF and asserts the trigger row
  survives, with an app.New contrast leg (called directly — a full
  daemon RunServe keeps goroutines holding the store briefly past
  return, racing the verification open).
- TestOpenCommandReadSourceLegacyDoesNotRepairStore (cmd) pins the
  read-only CLI contract.

GOWORK=off go test ./cmd ./internal/app ./internal/tools -count=1 green;
new tests also green under -race -count=3.

Co-Authored-By: Claude Fable 5 <[email protected]>
@MaxGhenis
MaxGhenis merged commit 5f50c95 into main Jul 21, 2026
5 checks passed
@MaxGhenis
MaxGhenis deleted the claude/competent-vaughan-959e2b branch July 21, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant