can1357/oh-my-pi · error · Error
WAL checkpoint failed for ${dbPath}: busy=${result.busy}, wa
Error message
WAL checkpoint failed for ${dbPath}: busy=${result.busy}, walBytes=${result.walBytes} What it means
After attempting a WAL checkpoint on the stats SQLite database, the code requires success: zero busy frames and a zero-byte WAL file. If either remains non-zero, other connections still hold the database and unflushed WAL data, so it throws rather than deleting/truncating a database that is still in use.
Source
Thrown at packages/coding-agent/src/cli/gc-cli.ts:1336
let checkpointAttempted = false;
try {
db.run("PRAGMA busy_timeout = 5000");
const row = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get() as WalCheckpointRow | null;
checkpointAttempted = true;
result.busy = sqliteNumber(row?.busy);
result.log = sqliteNumber(row?.log);
result.checkpointedFrames = sqliteNumber(row?.checkpointed);
} finally {
db.close();
}
try {
result.walBytes = (await fs.stat(walPath)).size;
} catch (error) {
if (codeOf(error) !== "ENOENT") throw error;
result.walBytes = 0;
}
if (checkpointAttempted && (result.busy > 0 || result.walBytes > 0)) {
throw new Error(`WAL checkpoint failed for ${dbPath}: busy=${result.busy}, walBytes=${result.walBytes}`);
}
result.checkpointed = checkpointAttempted;
return result;
}
async function runWalGc(options: ResolvedGcOptions): Promise<WalGcResult> {
const databases = await Promise.all(
[getHistoryDbPath(options.agentDir), getModelDbPath(options.agentDir)].map(dbPath =>
checkpointWal(dbPath, options.apply),
),
);
return {
databases,
walBytes: databases.reduce((total, db) => total + db.walBytes, 0),
wouldCheckpoint: databases.some(db => db.wouldCheckpoint),
checkpointed: databases.some(db => db.checkpointed),
};
}View on GitHub (pinned to 9690622007)
Solutions
- Close other omp processes (TUI, stats dashboard, workers) holding the stats DB open, then re-run GC
- Retry the checkpoint after the readers exit — WAL checkpoint TRUNCATE succeeds once no other connection is active
- Check for stale/hung processes (`lsof` on the db/wal files) and kill them before retrying
- Move the DB off network filesystems if checkpointing persistently fails
Example fix
// before (fails while dashboard holds the DB) omp stats sync && omp gc --wal // after # stop the dashboard/TUI, then omp gc --wal
Defensive patterns
Strategy: retry
Validate before calling
import { Database } from "bun:sqlite";
const db = new Database(dbPath, { readonly: true });
const busy = db.query("PRAGMA wal_checkpoint(PASSIVE)").get();
db.close();
if (busy && (busy.busy > 0)) throw new Error("stats DB is in use; close other omp processes before GC"); Try / catch
try {
await checkpointWal(dbPath);
} catch (err) {
if (err instanceof Error && err.message.startsWith("WAL checkpoint failed")) {
// other connections hold the DB — wait for readers to exit, then retry
await Bun.sleep(1000);
await checkpointWal(dbPath);
} else throw err;
} Prevention
- Close the omp TUI / stats dashboard before running WAL GC
- Use a retry with backoff for checkpointing instead of treating busy>0 as fatal
- Watch for processes holding the db/-wal files open (lsof) on shared machines
- Keep the stats DB on a local filesystem, not network mounts
When it happens
Trigger: Running stats GC while another process (TUI, stats dashboard, worker) keeps an open SQLite connection that wrote to the WAL between checkpoint and verification, leaving busy>0 or walBytes>0.
Common situations: `omp gc` executed while the omp TUI or `omp stats` dashboard is running; a crashed process leaving a hot WAL plus a live lock holder; network filesystems where checkpointing behaves poorly.
Related errors
- GC already running: ${lockPath}
- Failed to open auth database at '${dbPath}' after ${maxAttem
- session header changed during stats cleanup: ${session.path}
- native spelling thread stopped
- Agent is already processing. Use steer() or followUp() to qu
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/5a5c1ab432d52a5c.
Report an issue: GitHub.