fix(memory): handle ENOENT gracefully in readFile instead of throwing

When a memory file doesn't exist yet (e.g. daily log `2026-02-19.md`),
`readFile` now returns `{ text: "", path }` instead of propagating the
ENOENT error. This prevents noisy error responses from the memory read
tool and aligns with the "graceful degradation" recommendation in #9307.

Closes #9307

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel Zou
2026-02-19 01:35:28 -05:00
committed by Vignesh
parent 8f80e2a467
commit f3f47886ba
2 changed files with 34 additions and 3 deletions

View File

@@ -12,7 +12,7 @@ let searchImpl: () => Promise<unknown[]> = async () => [
source: "memory" as const,
},
];
let readFileImpl: () => Promise<string> = async () => "";
let readFileImpl: () => Promise<unknown> = async () => "";
const stubManager = {
search: vi.fn(async () => await searchImpl()),
@@ -59,7 +59,7 @@ beforeEach(() => {
source: "memory" as const,
},
];
readFileImpl = async () => "";
readFileImpl = async () => ""; // default: return empty string
vi.clearAllMocks();
});
@@ -189,4 +189,23 @@ describe("memory tools", () => {
error: "path required",
});
});
it("returns empty text without error when file does not exist (ENOENT)", async () => {
readFileImpl = async () => {
return { text: "", path: "memory/2026-02-19.md" };
};
const cfg = { agents: { list: [{ id: "main", default: true }] } };
const tool = createMemoryGetTool({ config: cfg });
expect(tool).not.toBeNull();
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("call_enoent", { path: "memory/2026-02-19.md" });
expect(result.details).toEqual({
text: "",
path: "memory/2026-02-19.md",
});
});
});

View File

@@ -437,7 +437,19 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
if (!absPath.endsWith(".md")) {
throw new Error("path required");
}
const stat = await fs.lstat(absPath);
let stat;
try {
stat = await fs.lstat(absPath);
} catch (err: unknown) {
if (
err instanceof Error &&
"code" in err &&
(err as NodeJS.ErrnoException).code === "ENOENT"
) {
return { text: "", path: relPath };
}
throw err;
}
if (stat.isSymbolicLink() || !stat.isFile()) {
throw new Error("path required");
}