Skip to content

Commit 742f0c9

Browse files
zw-xyskclaude
andauthored
fix(memory): detect unindexed session transcripts in status mode (fixes #97814) (#97857)
* fix(memory): detect unindexed session transcripts in status mode (fixes #97814) The status-purpose MemoryIndexManager init skips ensureSessionStartupCatchup(), so openclaw memory status reports dirty=false while unindexed session files exist on disk. This is a false-clean state: the operator sees no backlog, but memory search silently misses unindexed transcripts. Fix: run markSessionStartupCatchupDirtyFiles() in status mode. It checks for on-disk session files without corresponding memory_index_sources rows and sets sessionsDirty=true if found, without triggering a full sync. The full catchup+sync runs on the next non-transient manager cycle (CLI sync or normal runtime init). Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory): await status dirty detection before status() reads the flag (fixes #97814) The status-purpose MemoryIndexManager init skips ensureSessionStartupCatchup(), so openclaw memory status reports dirty=false while unindexed session files exist on disk. This is a false-clean state: the operator sees no backlog, but memory search silently misses unindexed transcripts. Fix: move status-mode markSessionStartupCatchupDirtyFiles() from the constructor (fire-and-forget via void) into the create() factory method where it is awaited. This guarantees sessionsDirty is set before any caller reads manager.status(). The detection does NOT trigger a sync, so status remains lightweight. Review: verified manually — unit tests 21/21 pass, compilation 0 errors. * test(memory): add regression test for status-purpose dirty detection (fixes #97814) Adds a regression test that exercises the actual purpose:'status' manager flow: create a session file without index row, create status manager, verify status().dirty is true. This addresses the ClawSweeper P1 finding requesting a test that exercises the real status path, not only the protected helper. Also fixes the timing issue: move dirty detection from constructor (void) to create() factory (await), guaranteeing sessionsDirty is set before any caller reads manager.status(). --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6285f5c commit 742f0c9

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

extensions/memory-core/src/memory/index.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2290,4 +2290,26 @@ describe("memory index", () => {
22902290
restoreMemoryIndexStateDir();
22912291
}
22922292
});
2293+
it("status-purpose manager detects unindexed session transcripts as dirty", async () => {
2294+
// Regression test for #97814: plain openclaw memory status (purpose: status)
2295+
// must report dirty=true when session files exist without index rows.
2296+
const cfg = createCfg({ sources: ["sessions"], sessionMemory: true });
2297+
const stateDirName = ".state-status-dirty-test";
2298+
setMemoryIndexStateDir(path.join(workspaceDir, stateDirName));
2299+
try {
2300+
const sessionsDir = resolveSessionTranscriptsDirForAgent("main");
2301+
await fs.mkdir(sessionsDir, { recursive: true });
2302+
const transcriptPath = path.join(sessionsDir, "status-dirty-test.jsonl");
2303+
await fs.writeFile(transcriptPath, JSON.stringify({ type: "test", ts: 1 }) + "\n");
2304+
2305+
const manager = await getFreshManager(cfg, "status");
2306+
managersForCleanup.add(manager);
2307+
2308+
const result = manager.status();
2309+
expect(result.dirty).toBe(true);
2310+
} finally {
2311+
restoreMemoryIndexStateDir();
2312+
}
2313+
});
2314+
22932315
});

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,16 +355,29 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
355355
pending: INDEX_CACHE_PENDING,
356356
key,
357357
bypassCache: transient,
358-
create: async () =>
359-
new MemoryIndexManager({
358+
create: async () => {
359+
const manager = new MemoryIndexManager({
360360
cacheKey: key,
361361
cfg,
362362
agentId,
363363
workspaceDir,
364364
settings,
365365
providerRequirement,
366366
purpose: params.purpose,
367-
}),
367+
});
368+
// Lightweight dirty-file detection for status mode: check for unindexed
369+
// session files on disk without triggering a full sync. This runs before
370+
// any caller reads manager.status(), so the dirty flag is accurate when
371+
// status() reads sessionsDirty.
372+
if (purpose === "status" && manager.sources.has("sessions")) {
373+
try {
374+
await manager.markSessionStartupCatchupDirtyFiles();
375+
} catch (err) {
376+
log.warn("memory status session dirty detection failed: " + String(err));
377+
}
378+
}
379+
return manager;
380+
},
368381
});
369382
}
370383

0 commit comments

Comments
 (0)