fix(hooks): avoid session memory filename collisions

Add collision suffixes for session-memory fallback filenames so repeated same-minute reset/new captures do not overwrite earlier archives.
This commit is contained in:
Vincent Koc
2026-05-05 01:03:59 -07:00
committed by GitHub
parent f3d531439b
commit 61383aff4b
3 changed files with 59 additions and 1 deletions

View File

@@ -73,6 +73,7 @@ Docs: https://docs.openclaw.ai
- Gateway/status: show recent supervisor restart handoffs in `openclaw gateway status --deep`, including JSON details, so clean service-managed restarts are reported as restart handoffs instead of opaque stopped-service diagnostics. Thanks @shakkernerd.
- Providers/Fireworks: expose Kimi models as thinking-off-only and keep K2.5/K2.6 requests on `thinking: disabled`, so manual model switches do not send Fireworks-rejected `reasoning*` parameters. Refs #74289. Thanks @frankekn.
- WhatsApp responsiveness: stop only verified stale local TUI clients when they degrade the Gateway event loop and delay replies. Thanks @vincentkoc.
- Hooks/session-memory: add collision suffixes to fallback memory filenames so repeated `/new` or `/reset` captures in the same minute do not overwrite the earlier session archive. Thanks @vincentkoc.
- Video generation: wait up to 20 minutes for slow fal/MiniMax queue-backed jobs, stop forwarding unsupported Google Veo generated-audio options, and normalize MiniMax `720P` requests to its supported `768P` resolution with the usual override warning/details instead of failing fallback.
- Video generation: accept provider-specific aspect-ratio and resolution hints at the tool boundary, normalize `720P` to MiniMax's supported `768P`, and stop sending Google `generateAudio` on Gemini video requests so provider fallback can recover from model-specific parameter differences. Thanks @vincentkoc.
- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc.

View File

@@ -413,6 +413,41 @@ describe("session-memory hook", () => {
});
});
it("keeps same-minute fallback timestamp captures by adding a filename suffix", async () => {
await withEnvAsync({ TZ: "UTC" }, async () => {
const tempDir = await createCaseWorkspace("workspace");
const timestamp = new Date("2026-01-01T04:30:15.000Z");
await runNewWithPreviousSessionEntry({
tempDir,
timestamp,
previousSessionEntry: {
sessionId: "first-session",
},
});
await runNewWithPreviousSessionEntry({
tempDir,
timestamp,
previousSessionEntry: {
sessionId: "second-session",
},
});
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
expect(files).toHaveLength(2);
expect(files).toContain("2026-01-01-0430.md");
expect(files).toContain("2026-01-01-0430-2.md");
await expect(
fs.readFile(path.join(memoryDir, "2026-01-01-0430.md"), "utf-8"),
).resolves.toContain("- **Session ID**: first-session");
await expect(
fs.readFile(path.join(memoryDir, "2026-01-01-0430-2.md"), "utf-8"),
).resolves.toContain("- **Session ID**: second-session");
});
});
it("prefers workspaceDir from hook context when sessionKey points at main", async () => {
const mainWorkspace = await createCaseWorkspace("workspace-main");
const naviWorkspace = await createCaseWorkspace("workspace-navi");

View File

@@ -85,6 +85,28 @@ function formatLocalSessionTimestamp(date: Date): {
};
}
async function resolveAvailableMemoryFilename(params: {
memoryDir: string;
dateStr: string;
slug: string;
}): Promise<string> {
const basename = `${params.dateStr}-${params.slug}`;
let suffix = 1;
while (true) {
const filename = suffix === 1 ? `${basename}.md` : `${basename}-${suffix}.md`;
try {
await fs.access(path.join(params.memoryDir, filename));
suffix += 1;
} catch (err) {
if ((err as { code?: string }).code === "ENOENT") {
return filename;
}
throw err;
}
}
}
function resolveDisplaySessionKey(params: {
cfg?: OpenClawConfig;
workspaceDir?: string;
@@ -223,7 +245,7 @@ async function saveSessionMemoryNow(event: Parameters<HookHandler>[0]): Promise<
}
// Create filename with date and slug
const filename = `${dateStr}-${slug}.md`;
const filename = await resolveAvailableMemoryFilename({ memoryDir, dateStr, slug });
const memoryFilePath = path.join(memoryDir, filename);
log.debug("Memory file path resolved", {
filename,