Skip to content

Commit f771ba8

Browse files
committed
fix(memory): avoid destructive qmd collection rebinds
1 parent 688b72e commit f771ba8

File tree

3 files changed

+9
-21
lines changed

3 files changed

+9
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ Docs: https://docs.openclaw.ai
128128
- Agents/Compaction safeguard structure hardening: require exact fallback summary headings, sanitize untrusted compaction instruction text before prompt embedding, and keep structured sections when preserving all turns. (#25555) thanks @rodrigouroz.
129129
- Gateway/status self version reporting: make Gateway self version in `openclaw status` prefer runtime `VERSION` (while preserving explicit `OPENCLAW_VERSION` override), preventing stale post-upgrade app version output. (#32655) thanks @liuxiaopai-ai.
130130
- Memory/QMD index isolation: set `QMD_CONFIG_DIR` alongside `XDG_CONFIG_HOME` so QMD config state stays per-agent despite upstream XDG handling bugs, preventing cross-agent collection indexing and excess disk/CPU usage. (#27028) thanks @HenryLoenwind.
131+
- Memory/QMD collection safety: stop destructive collection rebinds when QMD `collection list` only reports names without path metadata, preventing `memory search` from dropping existing collections if re-add fails. (#36870) Thanks @Adnannnnnnna.
131132
- Memory/local embedding initialization hardening: add regression coverage for transient initialization retry and mixed `embedQuery` + `embedBatch` concurrent startup to lock single-flight initialization behavior. (#15639) thanks @SubtleSpark.
132133
- CLI/Coding-agent reliability: switch default `claude-cli` non-interactive args to `--permission-mode bypassPermissions`, auto-normalize legacy `--dangerously-skip-permissions` backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. Related to #28261. Landed from contributor PRs #28610 and #31149. Thanks @niceysam, @cryptomaltese and @vincentkoc.
133134
- ACP/ACPX session bootstrap: retry with `sessions new` when `sessions ensure` returns no session identifiers so ACP spawns avoid `NO_SESSION`/`ACP_TURN_FAILED` failures on affected agents. Related to #28786. Landed from contributor PR #31338. Thanks @Sid-Qin and @vincentkoc.

src/memory/qmd-manager.test.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ describe("QmdMemoryManager", () => {
369369
expect(addSessions?.[2]).toBe(path.join(stateDir, "agents", devAgentId, "qmd", "sessions"));
370370
});
371371

372-
it("rebinds managed collections when qmd only reports collection names", async () => {
372+
it("avoids destructive rebind when qmd only reports collection names", async () => {
373373
cfg = {
374374
...cfg,
375375
memory: {
@@ -401,25 +401,11 @@ describe("QmdMemoryManager", () => {
401401
await manager.close();
402402

403403
const commands = spawnMock.mock.calls.map((call: unknown[]) => call[1] as string[]);
404-
const removeSessions = commands.find(
405-
(args) =>
406-
args[0] === "collection" && args[1] === "remove" && args[2] === sessionCollectionName,
407-
);
408-
expect(removeSessions).toBeDefined();
409-
const removeWorkspace = commands.find(
410-
(args) =>
411-
args[0] === "collection" && args[1] === "remove" && args[2] === `workspace-${agentId}`,
412-
);
413-
expect(removeWorkspace).toBeDefined();
404+
const removeCalls = commands.filter((args) => args[0] === "collection" && args[1] === "remove");
405+
expect(removeCalls).toHaveLength(0);
414406

415-
const addSessions = commands.find((args) => {
416-
if (args[0] !== "collection" || args[1] !== "add") {
417-
return false;
418-
}
419-
const nameIdx = args.indexOf("--name");
420-
return nameIdx >= 0 && args[nameIdx + 1] === sessionCollectionName;
421-
});
422-
expect(addSessions).toBeDefined();
407+
const addCalls = commands.filter((args) => args[0] === "collection" && args[1] === "add");
408+
expect(addCalls).toHaveLength(0);
423409
});
424410

425411
it("migrates unscoped legacy collections before adding scoped names", async () => {

src/memory/qmd-manager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,8 +568,9 @@ export class QmdMemoryManager implements MemorySearchManager {
568568
private shouldRebindCollection(collection: ManagedCollection, listed: ListedCollection): boolean {
569569
if (!listed.path) {
570570
// Older qmd versions may only return names from `collection list --json`.
571-
// Rebind managed collections so stale path bindings cannot survive upgrades.
572-
return true;
571+
// Do not perform destructive rebinds when metadata is incomplete: remove+add
572+
// can permanently drop collections if add fails (for example on timeout).
573+
return false;
573574
}
574575
if (!this.pathsMatch(listed.path, collection.path)) {
575576
return true;

0 commit comments

Comments
 (0)