fix: avoid crash on memory embeddings errors (#1004)

This commit is contained in:
Peter Steinberger
2026-01-17 09:45:45 +00:00
parent a6deb0d9d5
commit 1a4313c2aa
6 changed files with 122 additions and 26 deletions

View File

@@ -60,6 +60,7 @@ Docs: https://docs.clawd.bot
- Sessions: propagate deliveryContext into last-route updates to keep account/channel routing stable. (#1058) - Sessions: propagate deliveryContext into last-route updates to keep account/channel routing stable. (#1058)
- Sessions: preserve overrides on `/new` reset. - Sessions: preserve overrides on `/new` reset.
- Memory: prevent unhandled rejections when watch/interval sync fails. (#1076) — thanks @roshanasingh4. - Memory: prevent unhandled rejections when watch/interval sync fails. (#1076) — thanks @roshanasingh4.
- Memory: avoid gateway crash when embeddings return 429/insufficient_quota (disable tool + surface error). (#1004)
- Gateway: honor explicit delivery targets without implicit accountId fallback; preserve lastAccountId for implicit routing. - Gateway: honor explicit delivery targets without implicit accountId fallback; preserve lastAccountId for implicit routing.
- Gateway: avoid reusing last-to/accountId when the requested channel differs; sync deliveryContext with last route fields. - Gateway: avoid reusing last-to/accountId when the requested channel differs; sync deliveryContext with last route fields.
- Build: allow `@lydell/node-pty` builds on supported platforms. - Build: allow `@lydell/node-pty` builds on supported platforms.

View File

@@ -0,0 +1,62 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("../../memory/index.js", () => {
return {
getMemorySearchManager: async () => {
return {
manager: {
search: async () => {
throw new Error("openai embeddings failed: 429 insufficient_quota");
},
readFile: async () => {
throw new Error("path required");
},
status: () => ({
files: 0,
chunks: 0,
dirty: true,
workspaceDir: "/tmp",
dbPath: "/tmp/index.sqlite",
provider: "openai",
model: "text-embedding-3-small",
requestedProvider: "openai",
}),
},
};
},
};
});
import { createMemoryGetTool, createMemorySearchTool } from "./memory-tool.js";
describe("memory tools", () => {
it("does not throw when memory_search fails (e.g. embeddings 429)", async () => {
const cfg = { agents: { list: [{ id: "main", default: true }] } };
const tool = createMemorySearchTool({ config: cfg });
expect(tool).not.toBeNull();
if (!tool) throw new Error("tool missing");
const result = await tool.execute("call_1", { query: "hello" });
expect(result.details).toEqual({
results: [],
disabled: true,
error: "openai embeddings failed: 429 insufficient_quota",
});
});
it("does not throw when memory_get fails", async () => {
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_2", { path: "memory/NOPE.md" });
expect(result.details).toEqual({
path: "memory/NOPE.md",
text: "",
disabled: true,
error: "path required",
});
});
});

View File

@@ -47,6 +47,7 @@ export function createMemorySearchTool(options: {
if (!manager) { if (!manager) {
return jsonResult({ results: [], disabled: true, error }); return jsonResult({ results: [], disabled: true, error });
} }
try {
const results = await manager.search(query, { const results = await manager.search(query, {
maxResults, maxResults,
minScore, minScore,
@@ -59,6 +60,10 @@ export function createMemorySearchTool(options: {
model: status.model, model: status.model,
fallback: status.fallback, fallback: status.fallback,
}); });
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return jsonResult({ results: [], disabled: true, error: message });
}
}, },
}; };
} }
@@ -91,12 +96,17 @@ export function createMemoryGetTool(options: {
if (!manager) { if (!manager) {
return jsonResult({ path: relPath, text: "", disabled: true, error }); return jsonResult({ path: relPath, text: "", disabled: true, error });
} }
try {
const result = await manager.readFile({ const result = await manager.readFile({
relPath, relPath,
from: from ?? undefined, from: from ?? undefined,
lines: lines ?? undefined, lines: lines ?? undefined,
}); });
return jsonResult(result); return jsonResult(result);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return jsonResult({ path: relPath, text: "", disabled: true, error: message });
}
}, },
}; };
} }

View File

@@ -75,8 +75,14 @@ export function registerMemoryCli(program: Command) {
defaultRuntime.log(error ?? "Memory search disabled."); defaultRuntime.log(error ?? "Memory search disabled.");
return; return;
} }
try {
await manager.sync({ reason: "cli", force: opts.force }); await manager.sync({ reason: "cli", force: opts.force });
defaultRuntime.log("Memory index updated."); defaultRuntime.log("Memory index updated.");
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
defaultRuntime.error(`Memory index failed: ${message}`);
process.exitCode = 1;
}
}); });
memory memory
@@ -105,10 +111,18 @@ export function registerMemoryCli(program: Command) {
defaultRuntime.log(error ?? "Memory search disabled."); defaultRuntime.log(error ?? "Memory search disabled.");
return; return;
} }
const results = await manager.search(query, { let results: Awaited<ReturnType<typeof manager.search>>;
try {
results = await manager.search(query, {
maxResults: opts.maxResults, maxResults: opts.maxResults,
minScore: opts.minScore, minScore: opts.minScore,
}); });
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
defaultRuntime.error(`Memory search failed: ${message}`);
process.exitCode = 1;
return;
}
if (opts.json) { if (opts.json) {
defaultRuntime.log(JSON.stringify({ results }, null, 2)); defaultRuntime.log(JSON.stringify({ results }, null, 2));
return; return;

View File

@@ -178,4 +178,10 @@ async function main() {
} }
} }
void main(); void main().catch((err) => {
console.error(
"[clawdbot] Gateway daemon failed:",
err instanceof Error ? (err.stack ?? err.message) : err,
);
process.exit(1);
});

View File

@@ -70,4 +70,7 @@ async function main() {
await program.parseAsync(process.argv); await program.parseAsync(process.argv);
} }
void main(); void main().catch((err) => {
console.error("[clawdbot] Relay failed:", err instanceof Error ? (err.stack ?? err.message) : err);
process.exit(1);
});