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,18 +47,23 @@ export function createMemorySearchTool(options: {
if (!manager) { if (!manager) {
return jsonResult({ results: [], disabled: true, error }); return jsonResult({ results: [], disabled: true, error });
} }
const results = await manager.search(query, { try {
maxResults, const results = await manager.search(query, {
minScore, maxResults,
sessionKey: options.agentSessionKey, minScore,
}); sessionKey: options.agentSessionKey,
const status = manager.status(); });
return jsonResult({ const status = manager.status();
results, return jsonResult({
provider: status.provider, results,
model: status.model, provider: status.provider,
fallback: status.fallback, model: status.model,
}); 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 });
} }
const result = await manager.readFile({ try {
relPath, const result = await manager.readFile({
from: from ?? undefined, relPath,
lines: lines ?? undefined, from: from ?? 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;
} }
await manager.sync({ reason: "cli", force: opts.force }); try {
defaultRuntime.log("Memory index updated."); await manager.sync({ reason: "cli", force: opts.force });
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>>;
maxResults: opts.maxResults, try {
minScore: opts.minScore, results = await manager.search(query, {
}); maxResults: opts.maxResults,
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);
});