Skip to content

Commit 152dc8e

Browse files
committed
fix(memory): preserve qmd startup errors
1 parent 7190fc4 commit 152dc8e

2 files changed

Lines changed: 56 additions & 9 deletions

File tree

extensions/memory-core/src/memory/search-manager.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,30 @@ describe("getMemorySearchManager caching", () => {
368368
expect(searchResults).toHaveLength(1);
369369
});
370370

371+
it("returns the qmd startup failure when builtin fallback is unavailable", async () => {
372+
const cfg = createQmdCfg("missing-qmd-no-builtin");
373+
checkQmdBinaryAvailability.mockResolvedValueOnce({
374+
available: false,
375+
reason: "binary",
376+
error: "spawn qmd ENOENT",
377+
});
378+
mockMemoryIndexGet.mockRejectedValueOnce(
379+
new Error(
380+
'Memory search unavailable: embedding provider "openai" is configured but unavailable.',
381+
),
382+
);
383+
384+
const result = await getMemorySearchManager({ cfg, agentId: "missing-qmd-no-builtin" });
385+
386+
expect(result.manager).toBeNull();
387+
expect(result.error).toContain("qmd binary unavailable (qmd): spawn qmd ENOENT");
388+
expect(result.error).toContain(
389+
'builtin fallback unavailable: Memory search unavailable: embedding provider "openai" is configured but unavailable.',
390+
);
391+
expect(createQmdManagerMock).not.toHaveBeenCalled();
392+
expect(mockMemoryIndexGet).toHaveBeenCalledTimes(1);
393+
});
394+
371395
it("treats legacy qmd unavailable results without a reason as binary failures", async () => {
372396
const cfg = createQmdCfg("missing-qmd-legacy");
373397
checkQmdBinaryAvailability.mockResolvedValueOnce({

extensions/memory-core/src/memory/search-manager.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,16 +262,18 @@ export async function getMemorySearchManager(params: {
262262
}
263263

264264
if (transient) {
265-
const { manager } = await createPrimaryQmdManager(
265+
const { manager, failureReason } = await createPrimaryQmdManager(
266266
params.purpose === "cli" ? "cli" : "status",
267267
);
268-
return manager ? { manager } : await getBuiltinMemorySearchManager(params);
268+
return manager
269+
? { manager }
270+
: await getBuiltinMemorySearchManagerAfterQmdFailure(params, failureReason);
269271
}
270272

271273
const recentFailure = getActiveQmdManagerOpenFailure(scopeKey, identityKey);
272274
if (recentFailure) {
273275
log.debug?.(`qmd memory unavailable; using builtin during cooldown: ${recentFailure.reason}`);
274-
return await getBuiltinMemorySearchManager(params);
276+
return await getBuiltinMemorySearchManagerAfterQmdFailure(params, recentFailure.reason);
275277
}
276278

277279
const pending = PENDING_QMD_MANAGER_CREATES.get(scopeKey);
@@ -280,16 +282,14 @@ export async function getMemorySearchManager(params: {
280282
return await getMemorySearchManager(params);
281283
}
282284

285+
let pendingFailureReason: string | undefined;
283286
const pendingCreate: PendingQmdManagerCreate = {
284287
identityKey,
285288
promise: (async () => {
286289
const created = await createFullQmdManager(identityKey);
287290
if (!created.entry) {
288-
recordQmdManagerOpenFailure(
289-
scopeKey,
290-
identityKey,
291-
created.failureReason ?? "qmd memory unavailable",
292-
);
291+
pendingFailureReason = created.failureReason ?? "qmd memory unavailable";
292+
recordQmdManagerOpenFailure(scopeKey, identityKey, pendingFailureReason);
293293
return null;
294294
}
295295
QMD_MANAGER_CACHE.set(scopeKey, created.entry);
@@ -308,12 +308,35 @@ export async function getMemorySearchManager(params: {
308308
};
309309
PENDING_QMD_MANAGER_CREATES.set(scopeKey, pendingCreate);
310310
const manager = await pendingCreate.promise;
311-
return manager ? { manager } : await getBuiltinMemorySearchManager(params);
311+
return manager
312+
? { manager }
313+
: await getBuiltinMemorySearchManagerAfterQmdFailure(params, pendingFailureReason);
312314
}
313315

314316
return await getBuiltinMemorySearchManager(params);
315317
}
316318

319+
async function getBuiltinMemorySearchManagerAfterQmdFailure(
320+
params: {
321+
cfg: OpenClawConfig;
322+
agentId: string;
323+
purpose?: MemorySearchManagerPurpose;
324+
},
325+
qmdFailureReason: string | undefined,
326+
): Promise<MemorySearchManagerResult> {
327+
const fallback = await getBuiltinMemorySearchManager(params);
328+
if (fallback.manager || !qmdFailureReason) {
329+
return fallback;
330+
}
331+
const fallbackError = fallback.error?.trim();
332+
return {
333+
manager: null,
334+
error: fallbackError
335+
? `${qmdFailureReason}; builtin fallback unavailable: ${fallbackError}`
336+
: qmdFailureReason,
337+
};
338+
}
339+
317340
async function getBuiltinMemorySearchManager(params: {
318341
cfg: OpenClawConfig;
319342
agentId: string;

0 commit comments

Comments
 (0)