fix: quiet missing daily memory reads

Closes #82928
This commit is contained in:
Galin Iliev
2026-05-25 07:42:57 -07:00
committed by GitHub
parent 026cfb6ba1
commit 277d8fece2
3 changed files with 74 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Gateway: keep session-only Control UI tool-start mirrors flowing during diagnostic queue pressure instead of silently dropping non-terminal tool updates.
- Agents/memory: return optional not-found context for missing date-only daily memory reads instead of logging benign first-run `ENOENT` failures. Fixes #82928. Thanks @galiniliev.
- Gateway: avoid sending duplicate tool-event frames to Control UI connections that are subscribed by both run and session.
- Discord/OpenAI voice: accept longer leading wake-name mistranscripts such as "Open Club" for OpenClaw.
- Agents/OpenAI-compatible: stop ModelStudio-compatible chat requests before sending system/tool-only payloads that have no usable user or assistant turn. (#86177) Thanks @TurboTheTurtle.

View File

@@ -60,6 +60,7 @@ type ReadTruncationDetails = {
const OFFSET_BEYOND_EOF_RE = /^Offset \d+ is beyond end of file \(\d+ lines total\)$/;
const READ_CONTINUATION_NOTICE_RE =
/\n\n\[(?:Showing lines [^\]]*?Use offset=\d+ to continue\.|\d+ more lines in file\. Use offset=\d+ to continue\.)\]\s*$/;
const DAILY_MEMORY_PATH_RE = /^memory\/\d{4}-\d{2}-\d{2}\.md$/;
function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
@@ -218,6 +219,40 @@ function emptyReadResult(): AgentToolResult<unknown> {
return { content: [textBlock], details: undefined };
}
function missingDailyMemoryReadResult(relativePath: string): AgentToolResult<unknown> {
return {
content: [
{
type: "text",
text: `No daily memory file exists yet at ${relativePath}.`,
},
],
details: {
status: "not_found",
path: relativePath,
optional: true,
},
};
}
function normalizeDailyMemoryReadPath(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
}
const normalized = value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
return DAILY_MEMORY_PATH_RE.test(normalized) ? normalized : undefined;
}
function isNotFoundError(error: unknown): boolean {
if (typeof (error as NodeJS.ErrnoException | undefined)?.code === "string") {
return (error as NodeJS.ErrnoException).code === "ENOENT";
}
if (!(error instanceof Error)) {
return false;
}
return /\bENOENT\b|no such file or directory|file not found/i.test(error.message);
}
async function executeReadPage(params: {
base: AnyAgentTool;
toolCallId: string;
@@ -230,6 +265,10 @@ async function executeReadPage(params: {
if (isOffsetBeyondEof(error, params.args)) {
return emptyReadResult();
}
const missingDailyMemoryPath = normalizeDailyMemoryReadPath(params.args.path);
if (missingDailyMemoryPath && isNotFoundError(error)) {
return missingDailyMemoryReadResult(missingDailyMemoryPath);
}
throw error;
}
}

View File

@@ -165,6 +165,40 @@ describe("FS tools with workspaceOnly=false", () => {
expect(JSON.stringify(result.content)).toContain("test read content");
});
it("returns optional not-found context for missing date-only daily memory reads", async () => {
const result = await runFsTool(
"read",
"test-call-missing-daily-memory",
{
path: "memory/2026-05-15.md",
},
undefined,
);
expect(result).toStrictEqual({
content: [
{
type: "text",
text: "No daily memory file exists yet at memory/2026-05-15.md.",
},
],
details: {
status: "not_found",
path: "memory/2026-05-15.md",
optional: true,
},
});
});
it("still throws for ordinary missing read paths", async () => {
const readTool = requireTool(toolsFor(undefined), "read");
await expect(
readTool.execute("test-call-missing-ordinary-file", {
path: "notes/missing.md",
}),
).rejects.toThrow(/ENOENT|no such file|not found/i);
});
it("should allow write outside workspace when workspaceOnly is unset", async () => {
const outsideUnsetFile = path.join(tmpDir, "outside-unset-write.txt");
await runFsTool(