fix: keep memory flush daily files append-only (#53725) (thanks @HPluseven)

This commit is contained in:
Vignesh Natarajan
2026-03-28 18:20:32 -07:00
committed by Vignesh
parent 9d1498b2c2
commit 7f46b03de0
2 changed files with 38 additions and 31 deletions

View File

@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
- LINE/status: stop `openclaw status` from warning about missing credentials when sanitized LINE snapshots are already configured, while still surfacing whether the missing field is the token or secret. (#45701) Thanks @tamaosamu.
- Gateway/health: carry webhook-vs-polling account mode from channel descriptors into runtime snapshots so passive channels like LINE and BlueBubbles skip false stale-socket health failures. (#47488) Thanks @karesansui-u.
- Memory/QMD: honor `memory.qmd.update.embedInterval` even when regular QMD update cadence is disabled or slower by arming a dedicated embed-cadence maintenance timer, while avoiding redundant timers when regular updates are already frequent enough. (#37326) Thanks @barronlroth.
- Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven.
## 2026.3.28-beta.1

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import type { Api, Model } from "@mariozechner/pi-ai";
import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
import { describe, expect, it, vi } from "vitest";
import type { AnyAgentTool } from "../../pi-tools.types.js";
const MEMORY_RELATIVE_PATH = "memory/2026-03-24.md";
@@ -69,47 +70,52 @@ describe("runEmbeddedAttempt memory flush tool forwarding", () => {
}
});
it("keeps the forwarded memory flush write tool append-only", async () => {
vi.resetModules();
const workspaceDir = await fs.mkdtemp(
path.join(os.tmpdir(), "openclaw-attempt-memory-flush-append-"),
);
it("activates the memory flush append-only write wrapper", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-attempt-memory-flush-"));
const memoryFile = path.join(workspaceDir, MEMORY_RELATIVE_PATH);
const stop = new Error("stop after append-only write check");
let appendOnlyWrite: Promise<unknown> | undefined;
try {
await fs.mkdir(path.dirname(memoryFile), { recursive: true });
await fs.writeFile(memoryFile, "seed", "utf-8");
vi.doMock("../../pi-tools.js", async () => {
const actual =
await vi.importActual<typeof import("../../pi-tools.js")>("../../pi-tools.js");
return {
...actual,
createOpenClawCodingTools: vi.fn((options) => {
const tools = actual.createOpenClawCodingTools(options);
const writeTool = tools.find((tool) => tool.name === "write");
expect(writeTool).toBeDefined();
appendOnlyWrite = writeTool!.execute("call-memory-flush", {
path: MEMORY_RELATIVE_PATH,
content: "new durable note",
});
throw stop;
}),
};
const { wrapToolMemoryFlushAppendOnlyWrite } = await import("../../pi-tools.read.js");
const fallbackWrite = vi.fn(async () => {
throw new Error("append-only wrapper should not delegate to the base write tool");
});
const writeTool: AnyAgentTool = {
name: "write",
description: "Write content to a file.",
parameters: { type: "object", properties: {} },
execute: fallbackWrite,
};
const wrapped = wrapToolMemoryFlushAppendOnlyWrite(writeTool, {
root: workspaceDir,
relativePath: MEMORY_RELATIVE_PATH,
});
const { runEmbeddedAttempt } = await import("./attempt.js");
await expect(runEmbeddedAttempt(createAttemptParams(workspaceDir))).rejects.toBe(stop);
await expect(appendOnlyWrite).resolves.toBeDefined();
await expect(
wrapped.execute("call-memory-flush-append", {
path: MEMORY_RELATIVE_PATH,
content: "new durable note",
}),
).resolves.toMatchObject({
content: [{ type: "text", text: `Appended content to ${MEMORY_RELATIVE_PATH}.` }],
details: {
path: MEMORY_RELATIVE_PATH,
appendOnly: true,
},
});
await expect(fs.readFile(memoryFile, "utf-8")).resolves.toBe("seed\nnew durable note");
await expect(
wrapped.execute("call-memory-flush-deny", {
path: "memory/other-day.md",
content: "wrong target",
}),
).rejects.toThrow(
`Memory flush writes are restricted to ${MEMORY_RELATIVE_PATH}; use that path only.`,
);
expect(fallbackWrite).not.toHaveBeenCalled();
} finally {
vi.doUnmock("../../pi-tools.js");
await fs.rm(workspaceDir, { recursive: true, force: true });
}
});