From 25093b7024bbb0b2696d7628b97fbb2a3835de43 Mon Sep 17 00:00:00 2001 From: zeroaltitude Date: Sun, 8 Mar 2026 17:37:43 -0700 Subject: [PATCH] fix: warn when blockSessionSave is cleared without sessionSaveContent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When blockSessionSave is pre-set, the handler skips transcript loading and LLM slug generation (avoiding unnecessary I/O and model calls). If a later hook clears blockSessionSave without setting sessionSaveContent, no file can be produced because the transcript was never loaded. Previously this was a silent no-op. Now emits log.warn so plugin authors know to supply sessionSaveContent when un-blocking a pre-set block. Adds test: 'blockSessionSave pre-set then cleared without sessionSaveContent warns and writes nothing' — verifies the edge case produces no file. --- .../bundled/session-memory/handler.test.ts | 32 +++++++++++++++++++ src/hooks/bundled/session-memory/handler.ts | 13 ++++++++ 2 files changed, 45 insertions(+) diff --git a/src/hooks/bundled/session-memory/handler.test.ts b/src/hooks/bundled/session-memory/handler.test.ts index 10c1ab98a62..98b0966629c 100644 --- a/src/hooks/bundled/session-memory/handler.test.ts +++ b/src/hooks/bundled/session-memory/handler.test.ts @@ -903,4 +903,36 @@ describe("session-memory hook", () => { const memoryFiles = (await fs.readdir(memoryDir)).filter((f) => f.endsWith(".md")); expect(memoryFiles).toHaveLength(0); }); + + it("blockSessionSave pre-set then cleared without sessionSaveContent warns and writes nothing", async () => { + const tempDir = await createCaseWorkspace("block-cleared-no-content"); + const sessionsDir = path.join(tempDir, "sessions"); + await fs.mkdir(sessionsDir, { recursive: true }); + const sessionFile = await writeWorkspaceFile({ + dir: sessionsDir, + name: "test-session.jsonl", + content: createMockSessionContent([{ role: "user", content: "will not be saved" }]), + }); + + const event = createHookEvent("command", "new", "agent:main:main", { + cfg: { agents: { defaults: { workspace: tempDir } } } satisfies OpenClawConfig, + previousSessionEntry: { sessionId: "s1", sessionFile }, + }); + + // Pre-set blockSessionSave — handler skips transcript loading + inline write + event.context.blockSessionSave = true; + + await handler(event); + + // A later hook clears blockSessionSave but forgets to set sessionSaveContent. + // Since the transcript was never loaded, no file can be produced. + event.context.blockSessionSave = false; + + await drainPostHookActions(event); + + // No memory file should exist — the transcript was never loaded + const memoryDir = path.join(tempDir, "memory"); + const memoryFiles = await fs.readdir(memoryDir).catch(() => [] as string[]); + expect(memoryFiles.filter((f) => f.endsWith(".md"))).toHaveLength(0); + }); }); diff --git a/src/hooks/bundled/session-memory/handler.ts b/src/hooks/bundled/session-memory/handler.ts index cb1d811e051..b32d990db4f 100644 --- a/src/hooks/bundled/session-memory/handler.ts +++ b/src/hooks/bundled/session-memory/handler.ts @@ -464,6 +464,19 @@ const saveSessionToMemory: HookHandler = async (event) => { log.debug("Session save content replaced by post-hook (sessionSaveContent)", { length: postContent.length, }); + } else if ( + event.context.blockSessionSave !== true && + writtenEntry === null && + typeof postContent !== "string" + ) { + // blockSessionSave was pre-set (causing writtenEntry=null and no inline + // write), then a later hook cleared it without providing sessionSaveContent. + // The transcript was never loaded, so we cannot produce a file. Warn so + // plugin authors know to supply content when un-blocking. + log.warn( + "blockSessionSave was cleared but no sessionSaveContent provided — " + + "no memory file written (transcript was not loaded during pre-set block)", + ); } }); } catch (err) {