fix(session-memory): blockSessionSave takes precedence over sessionSaveContent

When both flags are set, blockSessionSave must win — a blocked save
should never create a file, even if sessionSaveContent is also present.

Bug: if blockSessionSave was pre-set (writtenEntry=null), the post-hook
sessionSaveContent check would pass and create a new file, violating
the block intent.

Fix: guard the sessionSaveContent overwrite with blockSessionSave !== true.

Tests: 2 new tests covering both-flags-pre-set and both-flags-late-set.
Credit: Greptile review.
This commit is contained in:
zeroaltitude 2026-03-07 09:45:52 -07:00
parent 1ea1aa75df
commit 0621de4773
No known key found for this signature in database
GPG Key ID: 77592FB1C703882E
2 changed files with 61 additions and 1 deletions

View File

@ -746,4 +746,58 @@ describe("session-memory hook", () => {
const content = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
expect(content).toContain("important data");
});
it("blockSessionSave takes precedence over sessionSaveContent (both pre-set)", async () => {
const tempDir = await createCaseWorkspace("block-beats-content-pre");
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: "secret" }]),
});
const event = createHookEvent("command", "new", "agent:main:main", {
cfg: { agents: { defaults: { workspace: tempDir } } } satisfies OpenClawConfig,
previousSessionEntry: { sessionId: "s1", sessionFile },
});
event.context.blockSessionSave = true;
event.context.sessionSaveContent = "Should not appear";
await handler(event);
await drainPostHookActions(event);
const memoryDir = path.join(tempDir, "memory");
const memoryFiles = await fs.readdir(memoryDir).catch(() => [] as string[]);
expect(memoryFiles.filter((f) => f.endsWith(".md"))).toHaveLength(0);
});
it("blockSessionSave takes precedence over sessionSaveContent (both late-set)", async () => {
const tempDir = await createCaseWorkspace("block-beats-content-late");
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: "secret" }]),
});
const event = createHookEvent("command", "new", "agent:main:main", {
cfg: { agents: { defaults: { workspace: tempDir } } } satisfies OpenClawConfig,
previousSessionEntry: { sessionId: "s1", sessionFile },
});
// Handler writes inline (no flags set yet)
await handler(event);
// Later hooks set both flags
event.context.blockSessionSave = true;
event.context.sessionSaveContent = "Should not appear";
await drainPostHookActions(event);
const memoryDir = path.join(tempDir, "memory");
const memoryFiles = (await fs.readdir(memoryDir)).filter((f) => f.endsWith(".md"));
expect(memoryFiles).toHaveLength(0);
});
});

View File

@ -401,8 +401,14 @@ const saveSessionToMemory: HookHandler = async (event) => {
}
// If a later hook set sessionSaveContent, overwrite with new content.
// blockSessionSave takes precedence — never create/overwrite a file that
// was blocked, even if sessionSaveContent is also set.
const postContent = event.context.sessionSaveContent;
if (typeof postContent === "string" && postContent !== writtenEntry) {
if (
event.context.blockSessionSave !== true &&
typeof postContent === "string" &&
postContent !== writtenEntry
) {
await writeFileWithinRoot({
rootDir: memoryDir,
relativePath: filename,