Skip to content

fix(memory): detect missing qmd binary and warn user#28700

Closed
Jimmy-xuzimo wants to merge 2 commits intoopenclaw:mainfrom
Jimmy-xuzimo:fix/25910-qmd-binary-check
Closed

fix(memory): detect missing qmd binary and warn user#28700
Jimmy-xuzimo wants to merge 2 commits intoopenclaw:mainfrom
Jimmy-xuzimo:fix/25910-qmd-binary-check

Conversation

@Jimmy-xuzimo
Copy link
Copy Markdown
Contributor

Problem

When memory.backend is set to "qmd" but the qmd binary is not installed on the system, the gateway would silently fall back to the builtin memory backend without any visible warning. This caused confusion for users who thought QMD was working but were actually using the builtin backend.

Issue: #25910

Solution

This PR adds explicit detection and warning when the QMD binary is missing:

1. openclaw doctor Integration

  • Added checkQmdBinaryAvailable() function in backend-config.ts that verifies the qmd binary exists and is executable
  • Modified noteMemorySearchHealth() in doctor-memory-search.ts to check for QMD binary availability when backend is set to "qmd"
  • Shows a clear warning with actionable fix steps:
    • Link to QMD installation docs
    • Option to check configuration
    • Option to switch to builtin backend

2. Startup Warning

  • Modified getMemorySearchManager() in search-manager.ts to check QMD binary availability before attempting to create the manager
  • Logs a clear warning when QMD binary is not available, informing users about the fallback to builtin backend

3. Cross-Platform Support

  • Handles Windows .cmd extension automatically
  • Detects various "not found" error patterns across platforms (ENOENT, EINVAL on Windows, etc.)

Testing

Added 4 new test cases in backend-config.test.ts:

  • Returns available=false when binary not found
  • Returns available=true when binary is available
  • Respects custom timeout
  • Handles Windows .cmd extension

Added 2 new test cases in doctor-memory-search.test.ts:

  • Does not warn when QMD backend is active and binary is available
  • Warns when QMD backend is active but binary is not available

All tests pass:

✓ src/memory/backend-config.test.ts (11 tests)
✓ src/commands/doctor-memory-search.test.ts (10 tests)

Changes

  • src/memory/backend-config.ts: Added checkQmdBinaryAvailable() function
  • src/memory/backend-config.test.ts: Added tests for new function
  • src/commands/doctor-memory-search.ts: Integrated QMD binary check
  • src/commands/doctor-memory-search.test.ts: Added tests for doctor integration
  • src/memory/search-manager.ts: Added startup warning for missing QMD binary

Fixes #25910

The 🥱 (yawn) emoji has a negative connotation of boredom, which can be
interpreted as the AI being bored with the user's request. Changing it to
⏳ (hourglass) better represents the 'operation in progress but taking longer
than expected' meaning of the stallSoft state.

This also aligns the implementation with the documented default in the
StatusReactionEmojis type definition.

Fixes openclaw#28602
When memory.backend is set to "qmd" but the qmd binary is not installed,
the system now:

1. Shows a clear warning during 'openclaw doctor' with actionable fix steps
2. Logs a warning at startup when QMD fallback activates
3. Provides helpful error messages pointing to installation docs

This prevents silent failures where users think QMD is working but
it's actually falling back to builtin backend without notice.

Fixes openclaw#25910
@openclaw-barnacle openclaw-barnacle bot added commands Command implementations size: S labels Feb 27, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0601ad2a3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +369 to +373
const resolvedCommand = isWindows && !path.extname(command) ? `${command}.cmd` : command;

try {
// Try to run `qmd --version` to verify the binary works
const { stdout } = await execFileAsync(resolvedCommand, ["--version"], {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Probe QMD with a Windows-compatible process API

On Windows this probe appends .cmd (resolvedCommand) and then invokes it via execFile, but .cmd launch semantics require a shell/spawn path; this can report available: false even when QMD is correctly installed. In that case openclaw doctor and startup logs will incorrectly tell users QMD is missing and suggest fallback, which is a regression for Windows users with a valid QMD setup.

Useful? React with 👍 / 👎.

Comment on lines +31 to 35
if (!qmdCheck.available) {
log.warn(`QMD binary not available: ${qmdCheck.error}`);
log.warn("Falling back to builtin memory backend. To use QMD, install it and ensure it's on PATH.");
} else if (!statusOnly) {
const cached = QMD_MANAGER_CACHE.get(cacheKey);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse cached QMD manager even when availability probe fails

Cache reuse is now gated behind qmdCheck.available, so any probe false-negative skips the cached-manager fast path and always creates a fresh QMD manager. When the probe is wrong (for example on Windows due to .cmd probing), repeated calls rebuild and replace cache entries instead of reusing an existing manager, causing avoidable process churn and potential resource leakage from repeatedly superseded wrappers.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 27, 2026

Greptile Summary

adds QMD binary detection to prevent silent fallback when qmd command is missing. includes openclaw doctor integration and startup warnings.

Critical issue found:

  • src/memory/search-manager.ts:31-39 - control flow bug: logs "Falling back to builtin" but then tries creating QMD manager anyway, causing duplicate error messages and unnecessary work. fix: wrap QMD creation inside the else block

Minor issues:

  • src/memory/backend-config.ts:377 - unused version variable

Other notes:

  • test coverage is good for checkQmdBinaryAvailable() and doctor integration
  • missing tests for search-manager.ts integration with binary check
  • status-reactions.ts change (stallSoft emoji) is unrelated to QMD fix

Confidence Score: 2/5

  • has critical logic bug that causes duplicate warnings and inefficient fallback
  • score reflects critical control flow issue in search-manager.ts where binary unavailability check doesn't prevent QMD manager creation attempt, causing confusing double warnings and wasted work. the fix is straightforward (restructure if-else) but the bug affects core functionality
  • src/memory/search-manager.ts requires immediate attention to fix control flow logic

Last reviewed commit: 0601ad2

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +31 to 39
if (!qmdCheck.available) {
log.warn(`QMD binary not available: ${qmdCheck.error}`);
log.warn("Falling back to builtin memory backend. To use QMD, install it and ensure it's on PATH.");
} else if (!statusOnly) {
const cached = QMD_MANAGER_CACHE.get(cacheKey);
if (cached) {
return { manager: cached };
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

control flow doesn't skip QMD creation when binary unavailable. after logging "Falling back to builtin" at line 33, code continues to line 41 and tries creating QMD manager anyway. this will fail, log another error at line 68, then finally fall back to builtin. fix: wrap the QMD manager creation (lines 41-69) inside the else block so it only runs when binary is available

Suggested change
if (!qmdCheck.available) {
log.warn(`QMD binary not available: ${qmdCheck.error}`);
log.warn("Falling back to builtin memory backend. To use QMD, install it and ensure it's on PATH.");
} else if (!statusOnly) {
const cached = QMD_MANAGER_CACHE.get(cacheKey);
if (cached) {
return { manager: cached };
}
}
if (!qmdCheck.available) {
log.warn(`QMD binary not available: ${qmdCheck.error}`);
log.warn("Falling back to builtin memory backend. To use QMD, install it and ensure it's on PATH.");
// Skip QMD creation and fall through to builtin
} else {
if (!statusOnly) {
const cached = QMD_MANAGER_CACHE.get(cacheKey);
if (cached) {
return { manager: cached };
}
}
try {
const { QmdMemoryManager } = await import("./qmd-manager.js");
const primary = await QmdMemoryManager.create({
cfg: params.cfg,
agentId: params.agentId,
resolved,
mode: statusOnly ? "status" : "full",
});
if (primary) {
if (statusOnly) {
return { manager: primary };
}
const wrapper = new FallbackMemoryManager(
{
primary,
fallbackFactory: async () => {
const { MemoryIndexManager } = await import("./manager.js");
return await MemoryIndexManager.get(params);
},
},
() => QMD_MANAGER_CACHE.delete(cacheKey),
);
QMD_MANAGER_CACHE.set(cacheKey, wrapper);
return { manager: wrapper };
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
log.warn(`qmd memory unavailable; falling back to builtin: ${message}`);
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/search-manager.ts
Line: 31-39

Comment:
control flow doesn't skip QMD creation when binary unavailable. after logging "Falling back to builtin" at line 33, code continues to line 41 and tries creating QMD manager anyway. this will fail, log another error at line 68, then finally fall back to builtin. fix: wrap the QMD manager creation (lines 41-69) inside the `else` block so it only runs when binary is available

```suggestion
    if (!qmdCheck.available) {
      log.warn(`QMD binary not available: ${qmdCheck.error}`);
      log.warn("Falling back to builtin memory backend. To use QMD, install it and ensure it's on PATH.");
      // Skip QMD creation and fall through to builtin
    } else {
      if (!statusOnly) {
        const cached = QMD_MANAGER_CACHE.get(cacheKey);
        if (cached) {
          return { manager: cached };
        }
      }

      try {
        const { QmdMemoryManager } = await import("./qmd-manager.js");
        const primary = await QmdMemoryManager.create({
          cfg: params.cfg,
          agentId: params.agentId,
          resolved,
          mode: statusOnly ? "status" : "full",
        });
        if (primary) {
          if (statusOnly) {
            return { manager: primary };
          }
          const wrapper = new FallbackMemoryManager(
            {
              primary,
              fallbackFactory: async () => {
                const { MemoryIndexManager } = await import("./manager.js");
                return await MemoryIndexManager.get(params);
              },
            },
            () => QMD_MANAGER_CACHE.delete(cacheKey),
          );
          QMD_MANAGER_CACHE.set(cacheKey, wrapper);
          return { manager: wrapper };
        }
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        log.warn(`qmd memory unavailable; falling back to builtin: ${message}`);
      }
    }
```

How can I resolve this? If you propose a fix, please make it concise.

timeout: timeoutMs,
encoding: "utf8",
});
const version = stdout.trim().split("\n")[0];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version variable assigned but never used

Suggested change
const version = stdout.trim().split("\n")[0];
const { stdout } = await execFileAsync(resolvedCommand, ["--version"], {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/backend-config.ts
Line: 377

Comment:
`version` variable assigned but never used

```suggestion
    const { stdout } = await execFileAsync(resolvedCommand, ["--version"], {
```

How can I resolve this? If you propose a fix, please make it concise.

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 5, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because the author has more than 10 active PRs in this repo. Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands Command implementations r: too-many-prs size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: QMD backend fails silently when qmd binary is not installed

2 participants