From f96e1504502e9586d7b0cb2f95d7a0905f8ed9a9 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 31 Mar 2026 17:06:24 +0900 Subject: [PATCH] fix(doctor): suppress qmd session orphan cleanup (#58182) --- src/commands/doctor-state-integrity.test.ts | 44 +++++++++++++++++++++ src/commands/doctor-state-integrity.ts | 9 ++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/commands/doctor-state-integrity.test.ts b/src/commands/doctor-state-integrity.test.ts index 1dcb3e196f6..56369b47f08 100644 --- a/src/commands/doctor-state-integrity.test.ts +++ b/src/commands/doctor-state-integrity.test.ts @@ -161,6 +161,50 @@ describe("doctor state integrity oauth dir checks", () => { expect(files.some((name) => name.startsWith("orphan-session.jsonl.deleted."))).toBe(true); }); + it("suppresses orphan transcript warnings when QMD sessions are enabled", async () => { + const cfg: OpenClawConfig = { + memory: { + backend: "qmd", + qmd: { + sessions: { enabled: true }, + }, + }, + }; + setupSessionState(cfg, process.env, process.env.HOME ?? ""); + const sessionsDir = resolveSessionTranscriptsDirForAgent("main", process.env, () => tempHome); + fs.writeFileSync(path.join(sessionsDir, "orphan-session.jsonl"), '{"type":"session"}\n'); + + const confirmRuntimeRepair = vi.fn(async () => false); + await noteStateIntegrity(cfg, { confirmRuntimeRepair }); + + expect(stateIntegrityText()).not.toContain( + "These .jsonl files are no longer referenced by sessions.json", + ); + expect(confirmRuntimeRepair).not.toHaveBeenCalled(); + }); + + it("still detects orphan transcripts when QMD sessions are disabled", async () => { + const cfg: OpenClawConfig = { + memory: { + backend: "qmd", + qmd: { + sessions: { enabled: false }, + }, + }, + }; + setupSessionState(cfg, process.env, process.env.HOME ?? ""); + const sessionsDir = resolveSessionTranscriptsDirForAgent("main", process.env, () => tempHome); + fs.writeFileSync(path.join(sessionsDir, "orphan-session.jsonl"), '{"type":"session"}\n'); + + const confirmRuntimeRepair = vi.fn(async () => false); + await noteStateIntegrity(cfg, { confirmRuntimeRepair }); + + expect(stateIntegrityText()).toContain( + "These .jsonl files are no longer referenced by sessions.json", + ); + expect(confirmRuntimeRepair).toHaveBeenCalled(); + }); + it("prints openclaw-only verification hints when recent sessions are missing transcripts", async () => { const cfg: OpenClawConfig = {}; writeSessionStore(cfg, { diff --git a/src/commands/doctor-state-integrity.ts b/src/commands/doctor-state-integrity.ts index d4354246872..025f42e9086 100644 --- a/src/commands/doctor-state-integrity.ts +++ b/src/commands/doctor-state-integrity.ts @@ -16,6 +16,7 @@ import { resolveStorePath, } from "../config/sessions.js"; import { resolveRequiredHomeDir } from "../infra/home-dir.js"; +import { resolveMemoryBackendConfig } from "../plugin-sdk/memory-core-host-engine-storage.js"; import { parseAgentSessionKey } from "../sessions/session-key-utils.js"; import { note } from "../terminal/note.js"; import { shortenHomePath } from "../utils.js"; @@ -477,6 +478,11 @@ function shouldRequireOAuthDir(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boo return false; } +function shouldSuppressOrphanTranscriptWarning(cfg: OpenClawConfig, agentId: string): boolean { + const backendConfig = resolveMemoryBackendConfig({ cfg, agentId }); + return backendConfig?.backend === "qmd" && backendConfig.qmd?.sessions.enabled === true; +} + export async function noteStateIntegrity( cfg: OpenClawConfig, prompter: DoctorPrompterLike, @@ -502,6 +508,7 @@ export async function noteStateIntegrity( const requireOAuthDir = shouldRequireOAuthDir(cfg, env); const cloudSyncedStateDir = detectMacCloudSyncedStateDir(stateDir); const linuxSdBackedStateDir = detectLinuxSdBackedStateDir(stateDir); + const suppressOrphanTranscriptWarning = shouldSuppressOrphanTranscriptWarning(cfg, agentId); if (cloudSyncedStateDir) { warnings.push( @@ -779,7 +786,7 @@ export async function noteStateIntegrity( .filter((entry) => entry.isFile() && isPrimarySessionTranscriptFileName(entry.name)) .map((entry) => path.resolve(path.join(sessionsDir, entry.name))) .filter((filePath) => !referencedTranscriptPaths.has(filePath)); - if (orphanTranscriptPaths.length > 0) { + if (orphanTranscriptPaths.length > 0 && !suppressOrphanTranscriptWarning) { const orphanCount = countLabel(orphanTranscriptPaths.length, "orphan transcript file"); const orphanPreview = formatFilePreview(orphanTranscriptPaths); warnings.push(