Skip to content

Commit ee66b80

Browse files
author
Karl (via Dazlarus)
committed
fix: checkpoint WAL on live per-agent DB after publishMemoryDatabaseTables
The WAL checkpoint was inside writeMeta() which runs against the shadow/temp database during full reindex. The meta row gets copied to the live per-agent DB by publishMemoryDatabaseTables, so the checkpoint must happen on originalDb (the live DB) after publish returns — not on the shadow DB where writeMeta wrote it. This ensures the meta row is durable on disk before the process moves on. Without it, a crash between publish and close can leave meta only in the WAL file, causing the next startup to declare the index missing. Updates test comments to reflect the new checkpoint location.
1 parent 05572d2 commit ee66b80

2 files changed

Lines changed: 20 additions & 16 deletions

File tree

extensions/memory-core/src/memory/manager-sync-ops.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2793,6 +2793,18 @@ export abstract class MemoryManagerSyncOps {
27932793
vectorExtensionPath: this.vector.extensionPath,
27942794
});
27952795

2796+
// Force WAL checkpoint on the live per-agent DB so the newly
2797+
// published meta row survives process crashes. Without this, the
2798+
// meta may live only in the WAL file; if the process is killed
2799+
// before closeMemoryDatabase() checkpoints, the next startup reads
2800+
// no meta and declares the index missing.
2801+
try {
2802+
originalDb.exec("PRAGMA wal_checkpoint(TRUNCATE)");
2803+
} catch {
2804+
// Non-fatal: checkpoint may fail in read-only or edge cases.
2805+
// The normal close path will still attempt a checkpoint.
2806+
}
2807+
27962808
this.db = originalDb;
27972809
this.resetVectorState();
27982810
this.fts.available = nextFtsState.available;
@@ -2859,16 +2871,6 @@ export abstract class MemoryManagerSyncOps {
28592871
`INSERT INTO memory_index_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`,
28602872
)
28612873
.run(META_KEY, value);
2862-
// Force WAL checkpoint so meta survives process crashes.
2863-
// Without this, the meta row may live only in the WAL file; if the
2864-
// process is killed before closeMemoryDatabase() checkpoints, the
2865-
// next startup reads no meta and declares the index missing.
2866-
try {
2867-
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
2868-
} catch {
2869-
// Non-fatal: checkpoint may fail in read-only or edge cases.
2870-
// The normal close path will still attempt a checkpoint.
2871-
}
28722874
this.lastMetaSerialized = value;
28732875
}
28742876
}

extensions/memory-core/src/memory/manager.wal-checkpoint.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,11 @@ describe("writeMeta WAL checkpoint", () => {
112112
}
113113

114114
it("writeMeta calls PRAGMA wal_checkpoint(TRUNCATE) during sync", async () => {
115-
// Proves the fix: writeMeta itself forces a WAL checkpoint after writing
116-
// the meta row, independent of closeMemoryDatabase. On unpatched main,
117-
// the only checkpoint happens during close — meta can be lost if the
118-
// process is killed before close.
115+
// Proves the fix: after publishMemoryDatabaseTables copies the shadow
116+
// DB contents (including the meta row) to the live per-agent DB, a
117+
// WAL checkpoint is forced on the live DB — not on the shadow DB.
118+
// On unpatched main, the checkpoint was inside writeMeta on the shadow
119+
// DB and did nothing useful for crash durability of the live index.
119120
//
120121
// This test exercises the real per-agent DB path resolved through
121122
// resolveOpenClawAgentSqlitePath (not a legacy store.path override).
@@ -132,8 +133,9 @@ describe("writeMeta WAL checkpoint", () => {
132133
([sql]) => typeof sql === "string" && sql === "PRAGMA wal_checkpoint(TRUNCATE)",
133134
);
134135

135-
// At least one checkpoint fired during sync (from writeMeta).
136-
// Close-time checkpoint may add more, but the sync-time call is the fix.
136+
// At least one checkpoint fired during sync (from the post-publish
137+
// checkpoint on the live per-agent DB). Close-time checkpoint may add
138+
// more, but the sync-time call is the fix.
137139
expect(checkpointCalls.length).toBeGreaterThan(0);
138140

139141
execSpy.mockRestore();

0 commit comments

Comments
 (0)