Skip to content

fix(telegram): guard ingress worker JSON.parse against malformed Bot API bodies#98372

Closed
miorbnli wants to merge 2 commits into
openclaw:mainfrom
miorbnli:fix/telegram-ingress-malformed-json
Closed

fix(telegram): guard ingress worker JSON.parse against malformed Bot API bodies#98372
miorbnli wants to merge 2 commits into
openclaw:mainfrom
miorbnli:fix/telegram-ingress-malformed-json

Conversation

@miorbnli

@miorbnli miorbnli commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

fetchJson in extensions/telegram/src/telegram-ingress-worker.runtime.ts parsed the Bot API response body with JSON.parse but did not catch the SyntaxError. A non-JSON body (HTML error page, empty, or truncated from a CDN, reverse proxy, or TLS MITM) made the SyntaxError bubble out of fetchJson into the long-poll loop, where isRecoverableTelegramNetworkError judged it non-recoverable (SyntaxError is not in the recoverable code/name/message sets). The loop re-threw, the isolated ingress worker exited, and the Telegram channel went silent until manual restart.

Why This Change Was Made

This is the same JSON.parse guard pattern already shipped for the other bundled surfaces this week: matrix (#97973), sms (#97999), signal (#98073), plugin-sdk (#98125), nodes. The Telegram isolated ingress worker was the remaining unguarded surface, and its failure mode is worse than a single failed message — it takes down the whole inbound channel.

The matrix-style guard alone (try/catch + throw a descriptive Error) is not sufficient here, because the long-poll loop decides retry vs exit via isRecoverableTelegramNetworkError. So the guard is split: a non-2xx response throws createTelegramGetUpdatesError({ errorCode }) so the loop preserves Bot API HTTP status semantics (5xx/429 -> retry, 409 -> propagate, 401/404 -> fatal), and an HTTP 200 with a non-JSON body throws a descriptive Error whose message matches the "malformed json" entry in RECOVERABLE_MESSAGE_SNIPPETS so polling/webhook contexts (allowMessageMatch: true) back off and retry while send context (allowMessageMatch: false) stays conservative.

User Impact

A Telegram Bot API endpoint that temporarily returns a non-JSON body (Cloudflare/intermediary HTML error, empty body, truncated response) no longer kills the isolated ingress worker. The worker backs off and retries like it does for other transient network errors, keeping the channel connected. Legitimate JSON responses, send-path behavior, and the happy path are unchanged.

Rebase note

Rebased onto current main (upstream/main advanced past the original base). No behavioral change: the two original commits apply cleanly; the only conflict was an insertion-order collision in network-errors.test.ts where main (#101258) added a delete/react/edit/action idempotent-contexts test at the same spot this branch adds its malformed-JSON tests. Resolved by keeping both — the upstream delete/react/edit/action test plus this PR'''s two new malformed-JSON tests. network-errors.ts and telegram-ingress-worker.runtime.ts applied unchanged (auto-merged).

Focused suite after rebase: node scripts/run-vitest.mjs extensions/telegram/src/network-errors.test.ts extensions/telegram/src/telegram-ingress-worker.runtime.test.ts -> 66 passed (60 network-errors + 6 ingress worker).

Real behavior proof

  • Behavior addressed: A non-JSON Bot API body (HTML error page / empty / truncated) threw an uncaught SyntaxError out of fetchJson; the long-poll loop judged it non-recoverable and the isolated ingress worker exited, silently dropping the Telegram channel.

  • Real environment tested: Linux x86_64, Node v24.14.0, commit 9b7e9322e8 on branch fix/telegram-ingress-malformed-json (current head, after the 4xx-control follow-up).

  • Exact steps or command run after this patch: node --import tsx verify-ingress-real.mjs — stands up a real local HTTP server standing in for the Telegram Bot API that returns a non-JSON 503 HTML body on every getUpdates call, then calls the real exported runTelegramIngressWorkerRuntime. globalThis.fetch is not replaced, so the production fetch path handles the request over a real socket. The port records every message the worker emits.

  • Evidence after fix: Terminal capture of node runtime verification (live stdout, copied verbatim — non-mocked, real sockets):

    $ node --import tsx verify-ingress-real.mjs
    real local HTTP server standing in for the Telegram Bot API (returns non-JSON 503 body)
    calling the real exported runTelegramIngressWorkerRuntime (globalThis.fetch is NOT mocked)
    
      [worker -> port] poll-start  offset=null
      [server recv] GET /bot000:fake-test-token/getUpdates  -> 503 non-JSON (HTML error page)
      [worker -> port] poll-error  message="Telegram getUpdates failed with HTTP 503"  errorCode=503
      [worker -> port] poll-start  offset=null
      [server recv] GET /bot000:fake-test-token/getUpdates  -> 503 non-JSON (HTML error page)
      [worker -> port] poll-error  message="Telegram getUpdates failed with HTTP 503"  errorCode=503
      [worker -> port] poll-start  offset=null
      [server recv] GET /bot000:fake-test-token/getUpdates  -> 503 non-JSON (HTML error page)
      [worker -> port] poll-error  message="Telegram getUpdates failed with HTTP 503"  errorCode=503
    
    HTTP requests received by server : 3
    poll-error events emitted        : 3
    verdict: worker SURVIVES the malformed Bot API body -> backs off and retries (channel NOT dropped)
    
  • Observed result after fix: Against real sockets (no mocked fetch), a non-JSON 503 Bot API body makes fetchJson throw createTelegramGetUpdatesError({ errorCode: 503 }) (message Telegram getUpdates failed with HTTP 503), which isTelegramServerError recognizes as 5xx, so the long-poll loop backs off and retries instead of exiting. Three consecutive malformed responses produced three poll-error events and three real HTTP requests — the worker stayed alive and kept the channel connected. The errorCode=503 carried on the emitted poll-error confirms Bot API HTTP status semantics are preserved across the worker boundary (409 would propagate, 401/404 stay fatal) — the 4xx-control behavior on the current head.

  • What was not tested: Live end-to-end against the real api.telegram.org edge returning HTML (the local HTTP server standing in for the Bot API is the closest non-mocked reproduction of the network path; the worker is the real production entry point with globalThis.fetch untouched).

Tests and validation

$ pnpm test extensions/telegram/src/network-errors.test.ts
 Test Files  1 passed (1)
      Tests  60 passed (60)

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

Added two regression cases to network-errors.test.ts: the malformed-JSON Error thrown by fetchJson for an HTTP 200 non-JSON body is recoverable for polling/webhook (backoff) and non-recoverable for send (conservative); and a 4xx malformed-body error stays non-recoverable (client error, no indefinite retry). fetchJson itself is a file-local worker entry, so its end-to-end guard behavior is covered by the non-mocked runTelegramIngressWorkerRuntime runtime proof above.

Risk checklist

  • User-visible behavior: Yes (intended) - a non-JSON Bot API body during polling now backs off and retries instead of killing the ingress worker; legitimate JSON and the send path are unchanged.
  • Config/env/migration behavior: No - no config or env surface touched.
  • Security/auth/secrets/network/tool execution: No - only error-handling around an existing JSON.parse; no new credential, network, or command-execution path.
  • Plugins/providers/channels/SDK: Yes (bounded) - touches the Telegram ingress worker (extensions/telegram/) and one snippet in the shared network-errors.ts; no public/plugin SDK API change. The "malformed json" snippet only matches the descriptive Error this same PR adds, and send context keeps allowMessageMatch=false so it cannot widen send-path retries.
  • Highest-risk area: the "malformed json" snippet lives in shared network-errors.ts - but it only matches a message string no existing caller produces today, and only takes effect when allowMessageMatch is true (polling/webhook/unknown), never in send.
  • Mitigation: 60 network-errors tests (incl. the malformed-JSON + 4xx regression cases asserting send stays false) + non-mocked runTelegramIngressWorkerRuntime runtime verification showing the worker survives repeated malformed Bot API bodies.

Closes: N/A (no existing issue)

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: XS labels Jul 1, 2026
@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 8, 2026, 12:19 AM ET / 04:19 UTC.

Summary
The PR wraps Telegram getUpdates JSON parsing, makes HTTP 200 malformed bodies retryable for polling/webhook while preserving non-2xx status semantics, and adds retry-classifier regression tests.

PR surface: Source +9, Tests +32. Total +41 across 3 files.

Reproducibility: yes. at source level. Current main still rethrows the raw JSON.parse error for HTTP 200 non-JSON getUpdates bodies and the worker rethrows non-retryable errors; the PR discussion also includes live local-server proof for the fixed path.

Review metrics: 1 noteworthy metric.

  • Retry classifier snippets: 1 added. The added snippet changes recoverability for non-send Telegram contexts, so maintainers should notice the scope 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:

  • [P2] Maintainer should accept or narrow the shared retry-classifier scope before merge.

Risk before merge

  • [P1] The new malformed-json snippet is shared by all non-send Telegram contexts, so maintainers should explicitly accept retry behavior beyond the ingress worker before merge.
  • [P1] A consistently malformed HTTP 200 custom Bot API endpoint would now keep the ingress worker in retry backoff instead of surfacing as a fatal worker exit.

Maintainer options:

  1. Accept the retry behavior (recommended)
    Land the PR once checks pass and maintainers are comfortable with HTTP 200 malformed getUpdates bodies staying in local retry backoff.
  2. Keep retry classification ingress-local
    Require a narrow typed ingress error path before merge if the shared non-send malformed-json snippet is too broad for webhook/action/edit/delete contexts.
  3. Close as covered by prior stability work
    Close or pause the PR if maintainers decide the merged non-2xx getUpdates recovery is enough and the HTTP 200 malformed case should not change.

Next step before merge

  • No automated repair is selected; the remaining action is maintainer acceptance of the availability/retry-scope choice plus normal merge gating.

Maintainer decision needed

  • Question: Should Telegram treat HTTP 200 non-JSON getUpdates bodies as transient retryable failures via the shared non-send malformed-json classifier?
  • Rationale: The patch appears correct, but it intentionally changes worker availability behavior and broadens one shared retry snippet across non-send Telegram contexts, which is a maintainer-owned risk choice.
  • Likely owner: steipete — steipete is the live assignee and has adjacent history in the Telegram network-errors helper, making him the best available owner for the merge-risk decision.
  • Options:
    • Accept status-aware retry (recommended): Land once normal merge gates pass, accepting HTTP 200 malformed getUpdates bodies as transient polling/webhook failures while 4xx and send paths remain conservative.
    • Narrow classifier first: Ask for an ingress-specific typed classification path if maintainers do not want a broad malformed-json snippet in the shared Telegram retry helper.
    • Pause behind merged stability work: Pause or close if maintainers decide the already-merged 5xx/429 recovery is sufficient and HTTP 200 malformed Bot API bodies should remain fatal.

Security
Cleared: No concrete security or supply-chain concern was found; the diff changes Telegram TypeScript error handling and tests without new dependencies, permissions, secrets handling, or code execution paths.

Review details

Best possible solution:

Land the status-aware malformed-body guard after maintainer acceptance of the shared retry-classifier scope, preserving fatal 4xx/409 handling and conservative send behavior.

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

Yes, at source level. Current main still rethrows the raw JSON.parse error for HTTP 200 non-JSON getUpdates bodies and the worker rethrows non-retryable errors; the PR discussion also includes live local-server proof for the fixed path.

Is this the best way to solve the issue?

Yes. Catching inside fetchJson is the narrowest maintainable owner boundary because it still has response.ok/status available, while the classifier tests preserve fatal 4xx/409 and conservative send behavior.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 827f2c44225e.

Label changes

Label justifications:

  • P2: This is a bounded Telegram availability bugfix for the ingress worker and shared retry helper, with limited surface area and no security/config migration impact.
  • merge-risk: 🚨 availability: Merging intentionally changes malformed HTTP 200 Bot API responses from fatal worker exits into retry backoff, and the retry snippet applies across non-send contexts.
  • 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 (live_output): The PR body and follow-up comments include copied live Node output from a real local HTTP server exercising the real ingress runtime over sockets, including the unique HTTP 200 malformed-body recovery path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and follow-up comments include copied live Node output from a real local HTTP server exercising the real ingress runtime over sockets, including the unique HTTP 200 malformed-body recovery path.
Evidence reviewed

PR surface:

Source +9, Tests +32. Total +41 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 2 11 2 +9
Tests 1 32 0 +32
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 43 2 +41

What I checked:

Likely related people:

  • obviyus: Merged PR history ties this person to the current getUpdates transient retry behavior and the overlapping ingress-worker runtime surface. (role: recent Telegram ingress and retry contributor; confidence: high; commits: 5a10a29807ab; files: extensions/telegram/src/telegram-ingress-worker.runtime.ts, extensions/telegram/src/network-errors.ts)
  • steipete: Live PR metadata shows steipete assigned, and git history shows Peter Steinberger touched the Telegram network-errors helper during extension lowercase/error-helper refactors. (role: assigned reviewer and recent retry-helper refactor contributor; confidence: medium; commits: 179ccb952cf2, 9314bb718084; files: extensions/telegram/src/network-errors.ts)
  • miorbnli: Beyond this PR, merged history shows miorbnli changed Telegram retry contexts in the same network-errors helper in fix(telegram): use idempotent retry context for delete/reaction #96612. (role: recent retry-helper contributor; confidence: medium; commits: ce15f348bbc4; files: extensions/telegram/src/network-errors.ts, extensions/telegram/src/send.ts)
  • scoootscooob: Git history shows scoootscooob moved the Telegram channel implementation into extensions, including the network-errors module lineage. (role: extension move contributor; confidence: low; commits: e5bca0832fbd; files: extensions/telegram/src/network-errors.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.
Review history (1 earlier review cycle)
  • reviewed 2026-07-03T00:56:08.295Z sha a523ead :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 1, 2026
miorbnli added a commit to miorbnli/openclaw that referenced this pull request Jul 1, 2026
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]>
miorbnli added a commit to miorbnli/openclaw that referenced this pull request Jul 1, 2026
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]>
@miorbnli miorbnli closed this Jul 1, 2026
@miorbnli miorbnli reopened this Jul 1, 2026
@steipete steipete self-assigned this Jul 1, 2026
@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. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 1, 2026
@miorbnli
miorbnli force-pushed the fix/telegram-ingress-malformed-json branch from a94f690 to b976ad4 Compare July 2, 2026 00:58
miorbnli added a commit to miorbnli/openclaw that referenced this pull request Jul 2, 2026
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]>
@miorbnli miorbnli closed this Jul 2, 2026
@miorbnli miorbnli reopened this Jul 2, 2026
@miorbnli

miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated the Real behavior proof section with non-mocked current-head evidence: a real local HTTP server standing in for the Telegram Bot API (returns non-JSON 503) + the real exported runTelegramIngressWorkerRuntime (globalThis.fetch is NOT replaced). Commit SHA updated to current head b976ad4.

@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.

@miorbnli

miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added the unique HTTP 200 malformed proof ClawSweeper requested:

Part 1 (real worker + real HTTP server, globalThis.fetch NOT replaced):

  • Server returns HTTP 200 with text/html body (non-JSON)
  • Real runTelegramIngressWorkerRuntime polls; 3 requests over 3.5s
  • Worker SURVIVES (3 poll-error events, no exit) — the unique fix this PR adds
    (main already handles HTTP 503 via createTelegramGetUpdatesError; this PR adds the HTTP 200 path)

Part 2 (real isRecoverableTelegramNetworkError/isRetryableTelegramApiError):

  • HTTP-200-malformed: polling recoverable=true webhook=true send=false
  • HTTP-401: recoverable=false retryable=false (fatal — 4xx not retried)
  • HTTP-409: retryable=false (propagate to parent)
  • HTTP-503: retryable=true (server error backoff — regression guard)

Evidence output from Part 1:
[worker->port] poll-error message="Telegram getUpdates returned malformed JSON (HTTP 200)"
worker SURVIVES (backoff retry)

Evidence output from Part 2:
send=false, 401 retryable=false, 409 retryable=false, 503 retryable=true

@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. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. and removed 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. 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
miorbnli force-pushed the fix/telegram-ingress-malformed-json branch from b976ad4 to a523ead Compare July 3, 2026 00:45
miorbnli added a commit to miorbnli/openclaw that referenced this pull request Jul 3, 2026
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]>
@clawsweeper clawsweeper Bot added 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: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. labels Jul 3, 2026
openclaw-clownfish Bot pushed a commit that referenced this pull request Jul 6, 2026
…ut (#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 #97973 / sms #97999 / signal #98073 /
telegram-ingress #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]>
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]>
miorbnli and others added 2 commits July 8, 2026 11:53
…API bodies

fetchJson in telegram-ingress-worker.runtime.ts parsed the Bot API response
body with JSON.parse but did not catch SyntaxError. A non-JSON body (HTML
error page, empty, or truncated from a CDN, reverse proxy, or TLS MITM) made
the SyntaxError bubble to the long-poll loop, where
isRecoverableTelegramNetworkError judged it non-recoverable (SyntaxError is
not in the recoverable code/name/message sets), so the loop re-threw and the
isolated ingress worker exited — silently dropping the Telegram channel until
manual restart.

Wrap JSON.parse in try/catch and throw a descriptive Error carrying statusCode
(matching the matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 pattern). Add
"malformed json" to RECOVERABLE_MESSAGE_SNIPPETS so the polling/webhook
contexts back off and retry instead of exiting; send context stays
conservative (allowMessageMatch defaults false) to avoid duplicate-message
retries.

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

ClawSweeper flagged that the malformed-json snippet is too broad: a 4xx
response (bad token, apiRoot misconfig) with a non-JSON body would match
the snippet and retry indefinitely instead of surfacing as a fatal polling
error.

Split the fetchJson catch: 4xx client errors produce a message without
"malformed json", so the polling loop treats them as non-recoverable
(fatal). Server/transient statuses (5xx, 2xx) keep the recoverable path
(backoff retry).

Add 4xx regression test.

Co-Authored-By: Claude <[email protected]>
@miorbnli
miorbnli force-pushed the fix/telegram-ingress-malformed-json branch from a523ead to 9b7e932 Compare July 8, 2026 04:08
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]>
@miorbnli miorbnli closed this Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. 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: XS 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