Skip to content

fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout#98381

Merged
openclaw-clownfish[bot] merged 5 commits into
openclaw:mainfrom
miorbnli:fix/qmd-mcporter-json-parse-guard
Jul 6, 2026
Merged

fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout#98381
openclaw-clownfish[bot] merged 5 commits into
openclaw:mainfrom
miorbnli:fix/qmd-mcporter-json-parse-guard

Conversation

@miorbnli

@miorbnli miorbnli commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

runQmdSearchViaMcporter in extensions/memory-core/src/memory/qmd-manager.ts parsed the mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch. When mcporter exits 0 but prints non-JSON to stdout (daemon warning, output truncated by maxOutputChars, CLI killed early, flag mismatch), the SyntaxError propagated uncaught. Wrapping it is not enough on its own: errors.ts formatErrorMessage walks the .cause chain into the user-visible path, so keeping the raw SyntaxError on Error.cause (whose message embeds a raw stdout snippet) still leaked that snippet through formatErrorMessage even when the thrown message was generic.

Why This Change Was Made

Same JSON.parse guard pattern shipped this week across matrix (#97973), sms (#97999), signal (#98073), plugin-sdk (#98125), and telegram ingress (#98372); parseListedCollections in this same file already guards its JSON.parse.

The thrown Error message and its .cause are both deliberately generic. JSON.parse's SyntaxError message embeds a snippet of the raw input (e.g. Unexpected token '<', "<html>..."), which is surfaced before session visibility filtering and could leak sensitive content. Placing that SyntaxError on .cause is unsafe because formatErrorMessage traverses .cause to build the user-visible message — so the cause now carries only mcporter stdout was not valid JSON, and the raw parse detail is not placed on the cause chain at all.

User Impact

A memory recall that hits a malformed mcporter response fails with a typed, diagnosable Error whose user-visible formatted message is generic — no raw subprocess stdout reaches the user through formatErrorMessage. Legitimate JSON, the v1/v2 tool fallback, and the happy path are unchanged.

Real behavior proof

  • Behavior addressed: A non-JSON mcporter stdout leaked raw input: first as an uncaught SyntaxError, and (after the initial wrap) through formatErrorMessage walking .cause into the SyntaxError's raw-input snippet.

  • Real environment tested: Linux x86_64, Node v24.14.0, commit ecfe4d38d1 on branch fix/qmd-mcporter-json-parse-guard (current head, after the cause-chain fix).

  • Exact steps or command run after this patch: node --import tsx verify-qmd-cause.mjs — imports the real formatErrorMessage from openclaw/plugin-sdk/error-runtime, runs a real JSON.parse on a sensitive non-JSON stdout, then compares formatErrorMessage over the current-head Error shape (generic cause) vs the pre-fix shape (SyntaxError on cause). The real QmdMemoryManager class path is covered by the vitest suite (148 tests, incl. wraps non-JSON mcporter stdout as a typed error driving createManager() -> manager.search() -> runQmdSearchViaMcporter -> guard with a mocked non-JSON mcporter stdout).

  • Evidence after fix: Terminal capture of node runtime verification (live stdout, copied verbatim):

    $ node --import tsx verify-qmd-cause.mjs
    imports the real formatErrorMessage from openclaw/plugin-sdk/error-runtime
    
    [step 1] JSON.parse(maliciousStdout) -> SyntaxError.message = "Unexpected token '<', \"<html><bod\"... is not valid JSON"
    
    [step 2] current-head error -> formatErrorMessage (user-visible path):
      "qmd mcporter returned non-JSON stdout | mcporter stdout was not valid JSON"
      leaks raw stdout? NO (generic, safe)
    
    [step 3] pre-fix error -> same formatErrorMessage:
      "qmd mcporter returned non-JSON stdout | Unexpected *** '<', \"<html><bod\"... is not valid JSON"
      leaks raw stdout? YES (the cause-chain leak this PR closes)
    
    [step 4] cause chain still present for developer diagnostics:
      fixedErr.cause.message = "mcporter stdout was not valid JSON"
    
    Verdict: current head surfaces a generic message via formatErrorMessage;
            the .cause chain no longer carries the raw stdout snippet to the user.
    
  • Observed result after fix: formatErrorMessage(current-head error) returns qmd mcporter returned non-JSON stdout | mcporter stdout was not valid JSON — no raw stdout, no <html> snippet, no sensitive bytes from the simulated stdout. The pre-fix shape under the same formatErrorMessage includes the SyntaxError's raw snippet (the leak this PR closes). The cause chain is still present for diagnostics, carrying only a generic message.

  • What was not tested: Live end-to-end against a real mcporter subprocess emitting a daemon warning over an actual QMD collection (the mcporter CLI is not available in CI/local; the patched class path is exercised by the 148-test vitest suite through the real QmdMemoryManager, and the formatErrorMessage cause-chain invariant is proven by the node runtime check above).

Tests and validation

$ pnpm test extensions/memory-core/src/memory/qmd-manager.test.ts
 Test Files  1 passed (1)
      Tests  148 passed (148)

$ pnpm tsgo:extensions:test
EXIT=0 (type check passed)

Regression case wraps non-JSON mcporter stdout as a typed error instead of a raw SyntaxError drives the real QmdMemoryManager via createManager() -> manager.search() with a mocked non-JSON mcporter stdout (0-exit) and asserts the rejection matches /non-JSON stdout/. The cause-chain redaction invariant (no raw stdout through formatErrorMessage) is proven by the node runtime check above.

Risk checklist

  • User-visible behavior: Yes (intended, bounded) - a malformed mcporter stdout now fails recall with a typed Error whose formatted message is generic; valid JSON and the happy path unchanged.
  • Config/env/migration behavior: No - no config or env surface touched.
  • Security/auth/secrets/network/tool execution: Yes (addressed) - neither the thrown message nor the .cause message embeds raw mcporter stdout or the SyntaxError raw-input snippet; formatErrorMessage (which walks .cause) can no longer surface it. Verified by the node runtime check across the cause chain.
  • Plugins/providers/channels/SDK: No - internal to the memory-core plugin; runQmdSearchViaMcporter keeps its return type.
  • Highest-risk area: raw subprocess stdout leaking into a surfaced error message via the .cause chain - resolved by giving both the message and the cause generic text (verified by formatErrorMessage over the current-head error shape).
  • Mitigation: 148 qmd-manager tests (incl. the malformed-stdout regression case asserting /non-JSON stdout/) + node runtime verification proving formatErrorMessage does not leak the raw snippet over the cause chain + tsgo green.

Closes: N/A (no existing issue)

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 8:57 PM ET / 00:57 UTC.

Summary
The PR wraps qmd mcporter stdout parsing in a generic error and adds a regression test for malformed stdout.

PR surface: Source +14, Tests +35. Total +49 across 2 files.

Reproducibility: yes. source-reproducible: current main parses successful mcporter stdout with a bare JSON.parse, and a zero-exit non-JSON stdout follows the tested createManager() -> manager.search() path. I did not reproduce it with a live mcporter binary.

Review metrics: 1 noteworthy metric.

  • User-visible error paths: 1 changed. The qmd mcporter parse failure is flattened through formatErrorMessage into memory_search unavailable details, so the generic cause matters before merge.

Stored data model
Persistent data-model change detected: serialized state: extensions/memory-core/src/memory/qmd-manager.ts, vector/embedding metadata: extensions/memory-core/src/memory/qmd-manager.test.ts, vector/embedding metadata: extensions/memory-core/src/memory/qmd-manager.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] Live end-to-end proof against an actual mcporter/QMD daemon warning was not run; the available proof simulates non-JSON stdout at the subprocess boundary and verifies the real formatter path.

Maintainer options:

  1. Decide the mitigation before merge
    Keep this as a narrow memory-core error-normalization and redaction fix, while leaving the broader qmd first-search stdout-contamination issue tracked separately.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No repair lane is needed because the current head addresses the prior cause-chain finding; normal maintainer review and merge gates remain.

Security
Cleared: The latest diff is security-sensitive but clears the prior concern: it keeps both the outer error and cause generic and does not touch secrets, dependencies, CI, install scripts, or permissions.

Review details

Best possible solution:

Keep this as a narrow memory-core error-normalization and redaction fix, while leaving the broader qmd first-search stdout-contamination issue tracked separately.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: current main parses successful mcporter stdout with a bare JSON.parse, and a zero-exit non-JSON stdout follows the tested createManager() -> manager.search() path. I did not reproduce it with a live mcporter binary.

Is this the best way to solve the issue?

Yes. The narrow parse-boundary guard plus generic cause is the maintainable fix here; filtering specific daemon text would miss variants, and preserving the raw SyntaxError cause would leak through formatErrorMessage.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 9238d9aeae8d.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied terminal output using the real formatErrorMessage path to show the current generic cause does not leak the simulated raw stdout, with focused qmd-manager test coverage for the changed class path.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes copied terminal output using the real formatErrorMessage path to show the current generic cause does not leak the simulated raw stdout, with focused qmd-manager test coverage for the changed class path.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove merge-risk: 🚨 security-boundary: Current PR review selected no merge-risk labels.
  • remove status: 🔁 re-review loop: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is a focused memory-core bug/security-hardening fix with limited blast radius and clear validation, not an urgent broken-channel regression.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes copied terminal output using the real formatErrorMessage path to show the current generic cause does not leak the simulated raw stdout, with focused qmd-manager test coverage for the changed class path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied terminal output using the real formatErrorMessage path to show the current generic cause does not leak the simulated raw stdout, with focused qmd-manager test coverage for the changed class path.
Evidence reviewed

PR surface:

Source +14, Tests +35. Total +49 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 15 1 +14
Tests 1 35 0 +35
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 50 1 +49

What I checked:

  • Root policy read: Read the full root AGENTS.md and applied its deep PR review, plugin-boundary, proof, and security-review requirements. (AGENTS.md:1, 9238d9aeae8d)
  • Scoped extension policy read: Read the scoped extensions guide; the touched memory-core plugin path remains inside the bundled plugin boundary and does not add core imports or plugin API surface. (extensions/AGENTS.md:1, 9238d9aeae8d)
  • Current main has the bare parse: Current main still calls JSON.parse(result.stdout) after a successful mcporter call, so zero-exit non-JSON stdout is source-reproducible at the reported boundary. (extensions/memory-core/src/memory/qmd-manager.ts:2722, 9238d9aeae8d)
  • Latest PR head redacts the cause chain: The latest PR head catches parse failure and throws qmd mcporter returned non-JSON stdout with a generic mcporter stdout was not valid JSON cause, so the raw JSON.parse SyntaxError is no longer on the user-visible cause chain. (extensions/memory-core/src/memory/qmd-manager.ts:2733, 6eb1289c47a8)
  • Formatter path traverses causes: formatErrorMessage appends nested Error cause messages, which is why replacing the raw SyntaxError cause matters for user-visible redaction. (src/infra/errors.ts:69, 9238d9aeae8d)
  • memory_search exposes formatted unavailable errors: The memory_search deadline wrapper returns formatErrorMessage(error) as the unavailable error string, so the qmd parse error can reach tool output if not redacted. (extensions/memory-core/src/tools.ts:160, 9238d9aeae8d)

Likely related people:

  • vignesh07: Commit 3317b49d3b52 introduced QMD searches via mcporter keep-alive, which is the central runtime path this PR hardens. (role: introduced adjacent behavior; confidence: medium; commits: 3317b49d3b52; files: extensions/memory-core/src/memory/qmd-manager.ts, packages/memory-host-sdk/src/host/backend-config.ts)
  • armanddp: Merged QMD 1.1+ mcporter compatibility work added and tested the current v2 query and v1 fallback behavior around runQmdSearchViaMcporter. (role: adjacent feature contributor; confidence: high; commits: ed381577741e, 207eb73920d2, bcee1f56e92a; files: extensions/memory-core/src/memory/qmd-manager.ts, extensions/memory-core/src/memory/qmd-manager.test.ts)
  • vincentkoc: Recent qmd/mcporter PR history includes the search-tool override and boundary review on rejected stdout-filter work in the same area. (role: recent QMD/mcporter contributor and reviewer; confidence: high; commits: c1013e8dc07b, c4989e27c722; files: extensions/memory-core/src/memory/qmd-manager.ts, extensions/memory-core/src/memory/qmd-manager.test.ts)
  • chinar-amrutkar: The shared cause-chain traversal in formatErrorMessage, which this PR must account for, was introduced through commit 3f67581e50a3. (role: adjacent error-format contributor; confidence: medium; commits: 3f67581e50a3; files: src/infra/errors.ts, src/infra/errors.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 1, 2026
@miorbnli
miorbnli force-pushed the fix/qmd-mcporter-json-parse-guard branch from eabd28a to b1c2a91 Compare July 1, 2026 05:02
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 1, 2026
@miorbnli miorbnli closed this Jul 1, 2026
@miorbnli miorbnli reopened this Jul 1, 2026
@miorbnli
miorbnli force-pushed the fix/qmd-mcporter-json-parse-guard branch from 49de54e to 4d208be Compare July 1, 2026 06:52
@miorbnli miorbnli closed this Jul 1, 2026
@miorbnli miorbnli reopened this Jul 1, 2026
@miorbnli miorbnli closed this Jul 1, 2026
@miorbnli miorbnli reopened this Jul 1, 2026
@miorbnli
miorbnli force-pushed the fix/qmd-mcporter-json-parse-guard branch from 4d208be to 88855a2 Compare July 2, 2026 01:04
@miorbnli

miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated Real behavior proof + added a cause-chain fix commit (ecfe4d3): formatErrorMessage walks the .cause chain, so the guard now gives .cause a generic message too. Non-mocked node proof uses the real formatErrorMessage to show no raw stdout leaks through the cause chain on the current head.

@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 2, 2026
@clawsweeper clawsweeper Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jul 2, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 2, 2026
miorbnli and others added 5 commits July 3, 2026 08:45
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse
outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout
(daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI
killed early, or flag mismatch) threw a raw SyntaxError that propagated
uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a
context-free SyntaxError with no hint of the actual mcporter failure.

Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet
(matching the guard pattern already used in parseListedCollections in this
file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 /
telegram-ingress openclaw#98372 JSON.parse guard series).

Co-Authored-By: Claude <[email protected]>
Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error
lint rule; the original SyntaxError is now chained, improving diagnosability.

Co-Authored-By: Claude <[email protected]>
…-boundary)

ClawSweeper flagged that the previous error message exposed raw mcporter
stdout (first 200 chars) before session visibility filtering, which could
leak sensitive content. Drop the stdout preview from the thrown message;
keep the original SyntaxError as `cause` for diagnostics so the parse-failure
reason is still reachable without surfacing unfiltered subprocess output.

Co-Authored-By: Claude <[email protected]>
…out leak)

The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its
message (e.g. Unexpected token '<', then the raw bytes). Including that
SyntaxError message in the thrown Error would surface unfiltered mcporter
stdout before session visibility filtering. Drop the parse-error message from
the thrown Error; keep the original SyntaxError as cause for developer
diagnostics only.

Co-Authored-By: Claude <[email protected]>
… via formatErrorMessage)

formatErrorMessage walks the .cause chain into the user-visible path.
Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw
stdout snippet through formatErrorMessage even with a generic message.
Give the cause a generic message too; the raw snippet no longer reaches
the user.

Co-Authored-By: Claude <[email protected]>
@miorbnli
miorbnli force-pushed the fix/qmd-mcporter-json-parse-guard branch from ecfe4d3 to 6eb1289 Compare July 3, 2026 00:46
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 3, 2026
@vincentkoc

Copy link
Copy Markdown
Member

No description provided.

@vincentkoc vincentkoc self-assigned this Jul 6, 2026
@openclaw-clownfish
openclaw-clownfish Bot merged commit 7d939b6 into openclaw:main Jul 6, 2026
122 of 128 checks passed
vincentkoc added a commit to zhangqueping/openclaw that referenced this pull request Jul 6, 2026
* origin/main: (1287 commits)
  fix(android): block loopback canvas navigation (openclaw#99874)
  fix(hooks): suppress unhandled stdout/stderr stream errors in gmail watcher (openclaw#100519)
  fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout (openclaw#98381)
  fix(build): fall back to tsx for build TypeScript scripts (openclaw#91262)
  feat(skills): suggest saving detected reusable workflows by default (openclaw#95477) (openclaw#100692)
  docs(changelog): remove generated release-note entries
  feat(telegram): offer BotFather web app flow in setup help and docs (openclaw#100540)
  fix(ios): unify Talk and Settings row typography on one branded detail row (openclaw#100515)
  feat(gateway): add persisted crash-loop breaker and fatal-config exit contract
  refactor(macos): lock and unify PortGuardian tunnel record persistence so concurrent app instances cannot lose orphan records (openclaw#100601)
  fix: stop reconnecting on protocol mismatch (openclaw#98414)
  fix(maint): reuse recent hosted gates after rebase (openclaw#100663)
  fix(ui): reopen web terminals without stale content (openclaw#100665)
  fix(browser): diagnose empty WSL2 Chrome replies (openclaw#100590)
  fix(ios): chat snaps back to bottom when scrolling to top via status-bar tap (openclaw#100502)
  Treat already-compacted CLI compaction as no-op (openclaw#99136)
  docs(changelog): remove direct main fix entry
  fix(feishu): strip internal tool-trace banners from outbound text (openclaw#98705)
  fix(message): thread --limit through to CLI formatter and surface provider pagination hints (openclaw#99089)
  fix(voyage): close response body stream when batch output JSONL parsing throws (openclaw#98840)
  ...

# Conflicts:
#	extensions/memory-wiki/package.json
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
…ut (openclaw#98381)

* fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout

runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse
outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout
(daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI
killed early, or flag mismatch) threw a raw SyntaxError that propagated
uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a
context-free SyntaxError with no hint of the actual mcporter failure.

Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet
(matching the guard pattern already used in parseListedCollections in this
file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 /
telegram-ingress openclaw#98372 JSON.parse guard series).

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): preserve cause in qmd mcporter JSON.parse guard

Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error
lint rule; the original SyntaxError is now chained, improving diagnosability.

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary)

ClawSweeper flagged that the previous error message exposed raw mcporter
stdout (first 200 chars) before session visibility filtering, which could
leak sensitive content. Drop the stdout preview from the thrown message;
keep the original SyntaxError as `cause` for diagnostics so the parse-failure
reason is still reachable without surfacing unfiltered subprocess output.

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak)

The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its
message (e.g. Unexpected token '<', then the raw bytes). Including that
SyntaxError message in the thrown Error would surface unfiltered mcporter
stdout before session visibility filtering. Drop the parse-error message from
the thrown Error; keep the original SyntaxError as cause for developer
diagnostics only.

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage)

formatErrorMessage walks the .cause chain into the user-visible path.
Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw
stdout snippet through formatErrorMessage even with a generic message.
Give the cause a generic message too; the raw snippet no longer reaches
the user.

Co-Authored-By: Claude <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…ut (openclaw#98381)

* fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout

runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse
outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout
(daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI
killed early, or flag mismatch) threw a raw SyntaxError that propagated
uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a
context-free SyntaxError with no hint of the actual mcporter failure.

Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet
(matching the guard pattern already used in parseListedCollections in this
file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 /
telegram-ingress openclaw#98372 JSON.parse guard series).

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): preserve cause in qmd mcporter JSON.parse guard

Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error
lint rule; the original SyntaxError is now chained, improving diagnosability.

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary)

ClawSweeper flagged that the previous error message exposed raw mcporter
stdout (first 200 chars) before session visibility filtering, which could
leak sensitive content. Drop the stdout preview from the thrown message;
keep the original SyntaxError as `cause` for diagnostics so the parse-failure
reason is still reachable without surfacing unfiltered subprocess output.

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak)

The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its
message (e.g. Unexpected token '<', then the raw bytes). Including that
SyntaxError message in the thrown Error would surface unfiltered mcporter
stdout before session visibility filtering. Drop the parse-error message from
the thrown Error; keep the original SyntaxError as cause for developer
diagnostics only.

Co-Authored-By: Claude <[email protected]>

* fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage)

formatErrorMessage walks the .cause chain into the user-visible path.
Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw
stdout snippet through formatErrorMessage even with a generic message.
Give the cause a generic message too; the raw snippet no longer reaches
the user.

Co-Authored-By: Claude <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: memory-core Extension: memory-core P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants