fix: warn when blockSessionSave is cleared without sessionSaveContent

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.
This commit is contained in:
zeroaltitude 2026-03-08 17:37:43 -07:00
parent 2509356e08
commit 25093b7024
No known key found for this signature in database
GPG Key ID: 77592FB1C703882E
2 changed files with 45 additions and 0 deletions

View File

@ -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);
});
});

View File

@ -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) {