Skip to content

fix(memory): atomic DB flushes + backup auto-restore for AgentDB corruption (#2584) - #2585

Merged
ruvnet merged 1 commit into
mainfrom
fix/agentdb-atomic-writes-backup-restore
Jul 6, 2026
Merged

fix(memory): atomic DB flushes + backup auto-restore for AgentDB corruption (#2584)#2585
ruvnet merged 1 commit into
mainfrom
fix/agentdb-atomic-writes-backup-restore

Conversation

@ruvnet

@ruvnet ruvnet commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Fixes #2584 — AgentDB (sql.js) database disk image is malformed under torn/concurrent full-image flushes.

Root cause (verified in-tree at 3.25.1)

The store db.export()s the entire image and rewrites it on each flush, and most live-store flushes were non-atomic (writeFileSync straight over memory.db) with no cross-process lock between the daemon and MCP server. A kill/OOM mid-write or overlapping full-image flushes tear the file; the corruption window scales with DB size (~185 MB in the report). The existing recoverMemoryDatabase() rebuilds from the corrupt image, so when .recover salvages nothing it also salvages nothing → every memory_store keeps erroring.

Fix (additive, fail-closed)

  1. Atomic writeswriteFileAtomic() (temp → fsyncrename, + best-effort dir fsync). writeFileRestricted() now routes through it (covers the three main-store flushes); also the decay-path flush and metrics-db.mjs (all 3 copies). A torn/interrupted/concurrent write can no longer leave a half-written image.
  2. Backup auto-restorerestoreMemoryDbFromBackup() picks the newest PRAGMA integrity_check=ok snapshot from .swarm/backups/, parks the corrupt live DB, and atomically installs it. Wired into recoverMemoryDatabase() so that when the in-place rebuild can't verify, recovery restores from backup instead of erroring — turning total loss into automatic recovery. (Periodic rotating backups already exist in memory-backup.ts.)
  3. Tests__tests__/memory-durability-2584.test.ts: atomic-writer semantics + a synthesized torn image (integrity_check fails, rebuild salvages nothing) that must recover via backup-restore. 6/6 pass.

Report accuracy note

The report's metrics-db.mjs cite targets .claude-flow/metrics.db, a separate file from .swarm/memory.db. It was genuinely non-atomic (fixed here) but wasn't the writer that tore memory.db — the live-store flushes in memory-initializer.ts were.

🤖 Generated with RuFlo

…uption (#2584)

Data-durability fix for AgentDB (sql.js) 'database disk image is malformed'
under torn/concurrent full-image flushes.

- Atomic writes: new writeFileAtomic() (temp -> fsync -> rename, + best-effort
  dir fsync) in fs-secure.ts; route writeFileRestricted() through it (covers the
  three main-store flushes) and the decay-path flush + metrics-db.mjs (all 3
  copies). A kill/OOM mid-write or a concurrent writer can no longer leave a
  half-written image.
- Backup auto-restore: restoreMemoryDbFromBackup() picks the newest
  PRAGMA integrity_check=ok backup from .swarm/backups/, parks the corrupt live
  DB, and atomically installs it. Wired into recoverMemoryDatabase() so that
  when the in-place rebuild salvages nothing (the reported case where sqlite
  .recover recovered 0 rows), recovery restores from backup instead of erroring
  every call. Periodic backup rotation already exists (memory-backup.ts).
- Tests: __tests__/memory-durability-2584.test.ts — atomic-writer semantics +
  a synthesized torn-image (integrity_check fails, rebuild salvages nothing)
  that must recover via backup-restore. 6/6 pass.

Note: the report's metrics-db.mjs cite targets .claude-flow/metrics.db, a
separate file from .swarm/memory.db; it was genuinely non-atomic (fixed here)
but was not the writer that tore memory.db — the live-store flushes in
memory-initializer.ts were.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01S7GYqnVUVxBfZ5W8znqry3
@ruvnet
ruvnet merged commit 626bb79 into main Jul 6, 2026
170 of 172 checks passed
ruvnet added a commit that referenced this pull request Jul 6, 2026
…#2584)

Data-durability fix for AgentDB (sql.js) 'database disk image is malformed'
under torn/concurrent full-image flushes (#2585):
- Atomic writes (temp -> fsync -> rename) for every full-image DB flush.
- Backup auto-restore on malformed open when the in-place rebuild salvages
  nothing — total loss becomes automatic recovery.
Additive, fail-closed, zero-regression.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01S7GYqnVUVxBfZ5W8znqry3
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.

AgentDB (sql.js) corruption: 185MB memory.db → "database disk image is malformed" under concurrent daemon+MCP writes; .recover salvages nothing

1 participant