ruflo doctor leaves an orphaned WAL sidecar that blocks all subsequent memory writes
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 72.8k
- Forks
- 8.6k
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 82
Description
# `ruflo doctor` leaves an orphaned WAL sidecar that blocks all subsequent memory writes
**Package:** `ruflo@3.38.20` (`@claude-flow/cli@3.38.20`)
**Repo:** https://github.com/ruvnet/claude-flow — file an issue at https://github.com/ruvnet/claude-flow/issues
**Related:** #2735 (the WAL-sidecar safety gate), #3024 (native bridge disabled on Windows)
## Summary
`ruflo doctor` opens the memory database with a native better-sqlite3 WAL
connection and exits without checkpointing, leaving a **0-byte** `memory.db-wal`
and a `memory.db-shm` on disk. The #2735 safety gate treats the mere *presence*
of those sidecars as proof of a live native writer, so every later
`ruflo memory store` in that project is refused — permanently, until the
sidecars are removed by hand.
The gate's own docstring states the assumption it relies on:
> sidecars ... removed only on the last connection's clean close
`doctor` violates that assumption. The sidecars are orphans, not evidence of a
live holder, so the heuristic misfires and the CLI reports a condition that is
not true.
## Environment
| | |
|---|---|
| ruflo | 3.38.20 |
| Node | v24.20.0 |
| npm | 11.19.0 |
| OS | Windows 11, 10.0.26200, win32 x64 |
| Install | local (`npm install ruflo@3.38.20`); also reproduces via `npx ruflo@latest` |
Reproduces with the background daemon **stopped**, so the daemon is not involved.
## Reproduction
```bash
# 0. start from a checkpointed database (no sidecars)
rm -f .swarm/memory.db-wal .swarm/memory.db-shm
# 1. a write succeeds
ruflo memory store --namespace repro --key antes --value v
# -> [OK] Data stored successfully
# 2. run doctor
ruflo doctor >/dev/null 2>&1
# -> leaves wal=0 bytes, shm=32768 bytes
# 3. the identical write now fails
ruflo memory store --namespace repro --key depois --value v
```
Step 3 output:
```
[ERROR] memory database has an active native WAL connection (found -wal/-shm
sidecar files) — refusing an unsafe sql.js whole-image write. Retry once the
native writer completes, or restore the native better-sqlite3 bridge.
Bridge unavailable: AgentDB native bridge disabled on Windows after #3024;
set CLAUDE_FLOW_ENABLE_NATIVE_BRIDGE_ON_WINDOWS=1 to opt in
```
Deleting the two sidecars restores writes immediately. No other command in the
CLI surface leaves them behind: bisecting `doctor`, `config list`, `status`,
`mcp status`, `daemon status`, `agent list`, `swarm status`, `session list` and
`hooks list` against a cleaned database, only `doctor` does.
## Evidence that no writer is actually live
A `wal_checkpoint(TRUNCATE)` against the same database, at the moment the write
is being refused, reports no contention and removes both sidecars:
```
{"busy":0,"log":0,"checkpointed":0}
```
`busy: 0` is SQLite reporting that no other connection holds the WAL. The
0-byte `-wal` says the same thing: there are no pending frames to recover.
## Root cause
`node_modules/@claude-flow/cli/dist/src/memory/memory-initializer.js:78`
```js
function hasNativeWalSidecars(dbPath) {
try {
return fs.existsSync(`${dbPath}-wal`) || fs.existsSync(`${dbPath}-shm`);
}
catch {
return true;
}
}
```
The gate is **presence-based**, not **liveness-based**. It cannot distinguish a
live native holder from an orphan left by an unclean close, so any command that
leaks sidecars bricks the write path for the whole project directory.
## Impact
- `memory store` — and every feature built on it — fails for the rest of the
project's life until a user manually deletes internal database files.
- The error message misdirects: it blames a live writer and the missing Windows
native bridge, so users chase `CLAUDE_FLOW_ENABLE_NATIVE_BRIDGE_ON_WINDOWS=1`,
which does not address the cause. Whether that variable "works" only reflects
whether sidecars happen to be present, making the failure look intermittent.
- `ruflo doctor`, the command users are told to run when something is wrong, is
what breaks it.
## Suggested fixes
1. **Make `doctor` close cleanly.** Run `PRAGMA wal_checkpoint(TRUNCATE)` and
close every connection it opens. This alone fixes the reported symptom.
2. **Make the gate liveness-based.** Probe with `wal_checkpoint(TRUNCATE)` and
treat `busy === 0` as "no live holder, safe to proceed" — the sidecars are
also removed as a side effect. This keeps #2735's guarantee (a real holder
still returns `busy !== 0` and the write is still refused) while removing the
false positive, with no platform-specific process scanning.
3. **Improve the message.** When the `-wal` is 0 bytes and a checkpoint reports
`busy: 0`, say the sidecars are stale and name the recovery, instead of
pointing at a native writer that does not exist.
## Workaround for users
Checkpoint and close before writing; this is safe because a real writer makes
`busy !== 0` and nothing is removed:
```js
const Database = require('better-sqlite3');
const db = new Database('.swarm/memory.db');
const r = db.pragma('wal_checkpoint(TRUNCATE)')[0];
db.close();
if (r.busy !== 0) throw new Error('a live writer holds the WAL — do not delete sidecars');
```
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with node_modules/@claude-flow/cli/dist/src/memory/memory-initializer.js:78 and reproduce the failure using the documented memory store, doctor, and follow-up store commands. Trace the database connections opened by ruflo doctor and the WAL-sidecar safety check. Done means doctor no longer leaves blocking orphan sidecars, writes succeed afterward, and a live native writer is still refused.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, sqlite, typescript
- Domain
- cli, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 62/100