fix(memory): prefer --mask over --glob for qmd collection pattern flag (#58736)

* fix(memory): prefer --mask over --glob for qmd collection pattern flag

qmd 2.0.1 silently ignores the --glob flag when creating collections,
causing all patterns (e.g. MEMORY.md, memory.md) to fall back to the
default **/*.md glob. This leads to collection conflicts when multiple
collections target the same workspace directory with different patterns.

The existing flag negotiation logic in addCollection() tries --glob
first (when collectionPatternFlag is null), and since qmd accepts the
flag without error, OpenClaw never falls back to --mask. The result is
that memory-root-{agent} gets created with **/*.md instead of MEMORY.md,
and memory-alt-{agent} fails with a duplicate path+pattern conflict.

Fix: default collectionPatternFlag to '--mask' so the working flag is
tried first. The fallback to --glob is preserved for older qmd versions
that may not support --mask.

* docs(changelog): note qmd collection flag fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
chi
2026-04-01 15:11:56 +08:00
committed by GitHub
parent bd6c017192
commit cad3da52c9
3 changed files with 15 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
- Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae
- Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana
- MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.
- Memory/QMD: prefer `--mask` over `--glob` when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.
- Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog
## 2026.3.31

View File

@@ -749,7 +749,10 @@ describe("QmdMemoryManager", () => {
const child = createMockChild({ autoClose: false });
const pathArg = args[2] ?? "";
const name = args[args.indexOf("--name") + 1] ?? "";
const pattern = args[args.indexOf("--glob") + 1] ?? args[args.indexOf("--mask") + 1] ?? "";
const globIdx = args.indexOf("--glob");
const maskIdx = args.indexOf("--mask");
const pattern =
(globIdx !== -1 ? args[globIdx + 1] : maskIdx !== -1 ? args[maskIdx + 1] : "") ?? "";
const hasConflict = [...legacyCollections.entries()].some(
([existingName, info]) =>
existingName !== name && info.path === pathArg && info.pattern === pattern,
@@ -827,7 +830,10 @@ describe("QmdMemoryManager", () => {
const child = createMockChild({ autoClose: false });
const pathArg = args[2] ?? "";
const name = args[args.indexOf("--name") + 1] ?? "";
const pattern = args[args.indexOf("--glob") + 1] ?? args[args.indexOf("--mask") + 1] ?? "";
const globIdx = args.indexOf("--glob");
const maskIdx = args.indexOf("--mask");
const pattern =
(globIdx !== -1 ? args[globIdx + 1] : maskIdx !== -1 ? args[maskIdx + 1] : "") ?? "";
const hasConflict = [...listedCollections.entries()].some(
([existingName, info]) =>
existingName !== name && info.path === pathArg && info.pattern === pattern,
@@ -887,7 +893,7 @@ describe("QmdMemoryManager", () => {
);
});
it("falls back to --mask when qmd collection add rejects --glob", async () => {
it("prefers --mask for collection add and falls back to --glob when --mask is rejected", async () => {
cfg = {
...cfg,
memory: {
@@ -909,10 +915,10 @@ describe("QmdMemoryManager", () => {
}
if (args[0] === "collection" && args[1] === "add") {
const child = createMockChild({ autoClose: false });
const flag = args.includes("--glob") ? "--glob" : args.includes("--mask") ? "--mask" : "";
const flag = args.includes("--mask") ? "--mask" : args.includes("--glob") ? "--glob" : "";
addFlagCalls.push(flag);
if (flag === "--glob") {
emitAndClose(child, "stderr", "unknown flag: --glob", 1);
if (flag === "--mask") {
emitAndClose(child, "stderr", "unknown flag: --mask", 1);
return child;
}
queueMicrotask(() => child.closeWith(0));
@@ -924,7 +930,7 @@ describe("QmdMemoryManager", () => {
const { manager } = await createManager({ mode: "full" });
await manager.close();
expect(addFlagCalls).toEqual(["--glob", "--mask", "--mask", "--mask"]);
expect(addFlagCalls).toEqual(["--mask", "--glob", "--glob", "--glob"]);
expect(logWarnMock).toHaveBeenCalledWith(
expect.stringContaining("retrying with legacy compatibility flag"),
);

View File

@@ -274,7 +274,7 @@ export class QmdMemoryManager implements MemorySearchManager {
private attemptedNullByteCollectionRepair = false;
private attemptedDuplicateDocumentRepair = false;
private readonly sessionWarm = new Set<string>();
private collectionPatternFlag: QmdCollectionPatternFlag | null = null;
private collectionPatternFlag: QmdCollectionPatternFlag | null = "--mask";
private constructor(params: {
cfg: OpenClawConfig;