openclaw - 💡(How to fix) Fix fix: qmd vector search silently falls back to FTS on first query after gateway restart [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#59808Fetched 2026-04-08 02:40:19
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Participants

Root Cause

In runQmdSearchViaMcporter(), ensureMcporterDaemonStarted() calls runMcporter(["daemon", "start"]) on the first invocation after restart. The mcporter daemon start command writes startup logs (e.g. [mcporter] Starting server... or Daemon already running (pid xxx).) to stdout instead of stderr. These lines are captured into result.stdout alongside the actual JSON query result, causing JSON.parse(result.stdout) to throw on the malformed concatenated input.

The bug only fires on the first call per gateway lifecycle because state.daemonStart is null after restart. Once the Promise resolves, subsequent calls skip the daemon start step and stdout is clean.

Fix Action

Fix / Workaround

The defensive patch above works, but the root cause should be addressed upstream:

Code Example

{
  "provider": "none",
  "fallback": {
    "from": "qmd",
    "reason": "Unexpected token 'm', \"[mcporter] \"... is not valid JSON"
  },
  "mode": "fts-only"
}

---

- const parsedUnknown = JSON.parse(result.stdout);
+ const cleanStdout = result.stdout
+   .split("\n")
+   .filter((line) => !line.trim().startsWith("[mcporter]"))
+   .join("\n")
+   .trim();
+ const parsedUnknown = JSON.parse(cleanStdout);
RAW_BUFFERClick to expand / collapse

Problem

After every gateway restart, the first memory_search call silently degrades to FTS-only mode instead of using the qmd vector search path. Subsequent calls work correctly.

Symptom:

{
  "provider": "none",
  "fallback": {
    "from": "qmd",
    "reason": "Unexpected token 'm', \"[mcporter] \"... is not valid JSON"
  },
  "mode": "fts-only"
}

Root Cause

In runQmdSearchViaMcporter(), ensureMcporterDaemonStarted() calls runMcporter(["daemon", "start"]) on the first invocation after restart. The mcporter daemon start command writes startup logs (e.g. [mcporter] Starting server... or Daemon already running (pid xxx).) to stdout instead of stderr. These lines are captured into result.stdout alongside the actual JSON query result, causing JSON.parse(result.stdout) to throw on the malformed concatenated input.

The bug only fires on the first call per gateway lifecycle because state.daemonStart is null after restart. Once the Promise resolves, subsequent calls skip the daemon start step and stdout is clean.

Steps to Reproduce

  1. Start or restart the OpenClaw gateway
  2. Immediately call memory_search (before any prior successful qmd query)
  3. Observe provider: "none" and fallback.reason containing "[mcporter]"

Proposed Fix

File: src/.../qmd-manager.ts (compiled: dist/qmd-manager-*.js)
Location: runQmdSearchViaMcporter(), just before JSON.parse(result.stdout)

- const parsedUnknown = JSON.parse(result.stdout);
+ const cleanStdout = result.stdout
+   .split("\n")
+   .filter((line) => !line.trim().startsWith("[mcporter]"))
+   .join("\n")
+   .trim();
+ const parsedUnknown = JSON.parse(cleanStdout);

Better Long-Term Fix

The defensive patch above works, but the root cause should be addressed upstream:

  • Option A (recommended): Have mcporter daemon start write all log output to stderr, keeping stdout reserved for structured JSON output only
  • Option B: Pass discardStdout: true (or equivalent) when calling runMcporter(["daemon", "start"]), since only the exit code matters for that sub-command
  • Option C: Ensure ensureMcporterDaemonStarted() and the subsequent call are fully independent invocations with separate stdout captures

Environment

  • OpenClaw 2026.4.1
  • mcporter 0.8.1
  • qmd 2.0.1
  • OS: Linux x64 (CPU-only build)

extent analysis

TL;DR

Apply the proposed fix to qmd-manager.ts to clean the stdout before parsing it as JSON, or address the root cause by modifying mcporter to write logs to stderr.

Guidance

  • Implement the proposed fix by filtering out lines starting with [mcporter] from result.stdout before parsing it as JSON.
  • Consider addressing the root cause by modifying mcporter to write logs to stderr instead of stdout.
  • If modifying mcporter is not feasible, ensure that ensureMcporterDaemonStarted() and the subsequent call are fully independent invocations with separate stdout captures.
  • Verify the fix by restarting the OpenClaw gateway and calling memory_search immediately after, checking that the provider is not "none" and the fallback.reason does not contain "[mcporter]".

Example

const cleanStdout = result.stdout
  .split("\n")
  .filter((line) => !line.trim().startsWith("[mcporter]"))
  .join("\n")
  .trim();
const parsedUnknown = JSON.parse(cleanStdout);

Notes

The proposed fix is a defensive patch that works around the issue, but addressing the root cause by modifying mcporter to write logs to stderr is a more robust solution.

Recommendation

Apply the proposed fix to qmd-manager.ts as a temporary workaround, and consider addressing the root cause by modifying mcporter to write logs to stderr in the long term.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING