From 53a8f474ee16f60441e9f5d8c4c9356fe11216a5 Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sat, 14 Feb 2026 15:41:48 -0800 Subject: [PATCH] Memory/QMD: handle fallback init failures gracefully --- CHANGELOG.md | 1 + src/memory/search-manager.test.ts | 18 ++++++++++++++++++ src/memory/search-manager.ts | 13 ++++++++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f168e810..a215c25dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai - Memory/QMD: make QMD result JSON parsing resilient to noisy command output by extracting the first JSON array from noisy `stdout`. - Memory/QMD: treat prefixed `no results found` marker output as an empty result set in qmd JSON parsing. (#11302) Thanks @blazerui. - Memory/QMD: make `memory status` read-only by skipping QMD boot update/embed side effects for status-only manager checks. +- Memory/QMD: keep original QMD failures when builtin fallback initialization fails (for example missing embedding API keys), instead of replacing them with fallback init errors. - Memory/QMD: pass result limits to `search`/`vsearch` commands so QMD can cap results earlier. - Memory/QMD: avoid reading full markdown files when a `from/lines` window is requested in QMD reads. - Memory/QMD: skip rewriting unchanged session export markdown files during sync to reduce disk churn. diff --git a/src/memory/search-manager.test.ts b/src/memory/search-manager.test.ts index 359133638..fd51bb35a 100644 --- a/src/memory/search-manager.test.ts +++ b/src/memory/search-manager.test.ts @@ -209,4 +209,22 @@ describe("getMemorySearchManager caching", () => { expect(results[0]?.path).toBe("MEMORY.md"); expect(fallbackSearch).toHaveBeenCalledTimes(1); }); + + it("keeps original qmd error when fallback manager initialization fails", async () => { + const retryAgentId = "retry-agent-no-fallback-auth"; + const cfg = { + memory: { backend: "qmd", qmd: {} }, + agents: { list: [{ id: retryAgentId, default: true, workspace: "/tmp/workspace" }] }, + } as const; + + mockPrimary.search.mockRejectedValueOnce(new Error("qmd query failed")); + mockMemoryIndexGet.mockRejectedValueOnce(new Error("No API key found for provider openai")); + + const first = await getMemorySearchManager({ cfg, agentId: retryAgentId }); + if (!first.manager) { + throw new Error("manager missing"); + } + + await expect(first.manager.search("hello")).rejects.toThrow("qmd query failed"); + }); }); diff --git a/src/memory/search-manager.ts b/src/memory/search-manager.ts index 5978753e4..9b8278e2d 100644 --- a/src/memory/search-manager.ts +++ b/src/memory/search-manager.ts @@ -191,9 +191,16 @@ class FallbackMemoryManager implements MemorySearchManager { if (this.fallback) { return this.fallback; } - const fallback = await this.deps.fallbackFactory(); - if (!fallback) { - log.warn("memory fallback requested but builtin index is unavailable"); + let fallback: MemorySearchManager | null; + try { + fallback = await this.deps.fallbackFactory(); + if (!fallback) { + log.warn("memory fallback requested but builtin index is unavailable"); + return null; + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + log.warn(`memory fallback unavailable: ${message}`); return null; } this.fallback = fallback;