From e263ff22e082b50ef4a1cc32ee6ab45e24e2fda8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 8 May 2026 13:27:11 +0100 Subject: [PATCH] refactor: reject unmigrated legacy transcript writes --- src/agents/transcript/session-manager.test.ts | 17 +++++++++++++++++ src/agents/transcript/transcript-state.ts | 16 +++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/agents/transcript/session-manager.test.ts b/src/agents/transcript/session-manager.test.ts index f7c4e722528..48441ca5d68 100644 --- a/src/agents/transcript/session-manager.test.ts +++ b/src/agents/transcript/session-manager.test.ts @@ -11,6 +11,7 @@ import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-d import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js"; import { openTranscriptSessionManager } from "./session-manager.js"; import { SessionManager } from "./session-transcript-contract.js"; +import { replaceTranscriptStateEventsSync } from "./transcript-state.js"; async function makeTempSessionFile(name = "session.jsonl"): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-transcript-session-")); @@ -81,6 +82,22 @@ describe("TranscriptSessionManager", () => { ).toThrow(/Legacy transcript has not been imported into SQLite/); }); + it("rejects runtime writes to unmigrated legacy session files", async () => { + const sessionFile = await makeTempSessionFile(); + + expect(() => + replaceTranscriptStateEventsSync(sessionFile, [ + { + type: "session", + version: 3, + id: "session-legacy-write", + timestamp: new Date(0).toISOString(), + cwd: "/tmp/workspace", + }, + ]), + ).toThrow(/Legacy transcript has not been imported into SQLite/); + }); + it("opens virtual sqlite transcript locators without resolving them as filesystem paths", async () => { await makeTempSessionFile(); const sessionFile = createSqliteSessionTranscriptLocator({ diff --git a/src/agents/transcript/transcript-state.ts b/src/agents/transcript/transcript-state.ts index b8f4d51b987..212c4179610 100644 --- a/src/agents/transcript/transcript-state.ts +++ b/src/agents/transcript/transcript-state.ts @@ -7,7 +7,6 @@ import { replaceSqliteSessionTranscriptEvents, resolveSqliteSessionTranscriptScopeForPath, } from "../../config/sessions/transcript-store.sqlite.js"; -import { DEFAULT_AGENT_ID } from "../../routing/session-key.js"; import type { FileEntry, SessionContext, @@ -49,11 +48,6 @@ function generateEntryId(byId: { has(id: string): boolean }): string { return randomUUID(); } -function resolveAgentIdFromTranscriptPath(sessionFile: string): string { - void sessionFile; - return DEFAULT_AGENT_ID; -} - function transcriptStateFromEntries(fileEntries: FileEntry[]): TranscriptState { const headerBeforeMigration = fileEntries.find((entry): entry is SessionHeader => entry.type === "session") ?? null; @@ -88,12 +82,20 @@ function resolveTranscriptWriteScope( : path.resolve(sessionFile); const header = entries.find((entry): entry is SessionHeader => entry.type === "session"); const existing = resolveSqliteSessionTranscriptScopeForPath({ transcriptPath }); + if (!isSqliteSessionTranscriptLocator(transcriptPath) && !existing) { + throw new Error( + `Legacy transcript has not been imported into SQLite: ${transcriptPath}. Run "openclaw doctor --fix" to build the session database.`, + ); + } + if (!existing) { + return undefined; + } const sessionId = header?.id ?? existing?.sessionId; if (!sessionId) { return undefined; } return { - agentId: existing?.agentId ?? resolveAgentIdFromTranscriptPath(sessionFile), + agentId: existing.agentId, sessionId, transcriptPath, };