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

  1. Close other omp processes (TUI, stats dashboard, workers) holding the stats DB open, then re-run GC
  2. Retry the checkpoint after the readers exit — WAL checkpoint TRUNCATE succeeds once no other connection is active
  3. Check for stale/hung processes (`lsof` on the db/wal files) and kill them before retrying
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/5a5c1ab432d52a5c. Report an issue: GitHub.