Skip to content

fix(thread-ownership): bound 409 response read and preserve cancel semantics#98941

Merged
steipete merged 2 commits into
openclaw:mainfrom
miorbnli:fix/unbounded-success-json-reads
Jul 18, 2026
Merged

fix(thread-ownership): bound 409 response read and preserve cancel semantics#98941
steipete merged 2 commits into
openclaw:mainfrom
miorbnli:fix/unbounded-success-json-reads

Conversation

@miorbnli

@miorbnli miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The thread-ownership plugin's 409 conflict path still used an unbounded resp.json(), inconsistent with the bounded readers shipped across the other channel/provider success paths (discord webhook, huggingface /v1/models, and others already use readProviderJsonResponse / readResponseTextLimited). A compromised or buggy forwarder returning a multi-GB 409 body would buffer the entire body before JSON.parse even runs, OOMing the agent.

Scope note: an earlier version of this branch also bounded the discord webhook success read and the huggingface /v1/models read. Those two were superseded upstream by #98098 (fix(providers): bound successful OAuth and webhook responses) and #101079 (fix(extensions/huggingface): bound model discovery JSON response read to prevent OOM), which both use the shared readProviderJsonResponse helper. This branch was rebased onto current main and now carries only the thread-ownership fix, which is still open on main (unbounded resp.json()).

Why This Change Was Made

Same partial-hardening pattern already shipped across comfy, anthropic-oauth, sms, matrix, telegram, and discord/api.ts. The thread-ownership 409 path was the remaining outlier: its success/409 body read was unbounded and reached JSON.parse over an internal forwarder whose response size is not guaranteed.

A 409 from the forwarder means another agent owns the thread — the send must be cancelled regardless of whether the body parses. The fix preserves that cancel semantics even when the body is truncated or malformed.

User Impact

Well-formed 409 responses unchanged. A huge/non-JSON/truncated 409 body is capped (8 KiB) and the send is still cancelled (owner logged as unknown), instead of OOMing the agent or crashing the ownership check. The prior behavior on a truncated 409 body was an unhandled SyntaxError from JSON.parse that fell into the outer catch and allowed the send — this fix makes a malformed 409 cancel correctly.

Real behavior proof

  • Behavior addressed: Unbounded thread-ownership 409 body read; OOM risk + lost cancel semantics on a truncated/malformed 409.

  • Real environment tested: Linux x86_64, Node v24.14.0, commit 47c03956dd on branch fix/unbounded-success-json-reads (rebased onto current main).

  • Exact steps or command run after this patch: node --import tsx verify-thread-ownership-real.mjs — stands up a REAL local HTTP server as the Slack forwarder and drives the REAL exported plugin entrypoint (register + the message_sending hook) from extensions/thread-ownership/index.ts. globalThis.fetch is NOT mocked: the production fetchWithSsrFGuard handles each request over a real socket (each case's local server confirms it served the 409). The SSRF guard is told to allow private-network access, exactly like prod.

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

    Driving the real exported plugin entrypoint (register + message_sending hook).
    globalThis.fetch is NOT mocked; each case uses its own real local HTTP server.
    
    Case 1 (well-formed 409 body):
      [PASS]   local server served the 409
      [PASS]   cancel=true — result={"cancel":true}
      [PASS]   owner parsed as 'other-agent' — [info] thread-ownership: cancelled send to C100:111 — owned by other-agent
    
    Case 2 (malformed/truncated 409 body):
      [PASS]   local server served the 409
      [PASS]   cancel=true — result={"cancel":true}
      [PASS]   owner falls back to 'unknown' — [info] thread-ownership: cancelled send to C101:222 — owned by unknown
    
    Case 3 (oversized 409 body, 64 KiB vs 8 KiB cap):
      [PASS]   local server served the 409
      [PASS]   cancel=true — result={"cancel":true}
      [PASS]   owner falls back to 'unknown' (64 KiB body truncated at 8 KiB cap -> unparseable) — [info] thread-ownership: cancelled send to C102:333 — owned by unknown
    
    Verdict: all cases PASS — well-formed 409 cancels with parsed owner; malformed + oversized 409 still cancel (owner 'unknown') without crash or unbounded buffering.
    
  • Observed result after fix: Against a real local forwarder, the real message_sending hook (driven through register) returns { cancel: true } for all three 409 shapes. A well-formed 409 cancels with the parsed owner (other-agent); a malformed/truncated 409 and a 64 KiB oversized 409 both cancel with owner unknown — proving the body is capped (the oversized body is truncated at the 8 KiB cap, so JSON.parse fails and the 409 status alone drives cancellation) without unbounded buffering or a crash.

  • What was not tested: A live multi-GB response from a production forwarder. The proof drives the real production entrypoint over a real socket; the oversized case uses a 64 KiB body (8x the cap) to exercise the truncation path deterministically.

Tests and validation

$ node scripts/run-vitest.mjs extensions/thread-ownership/index.test.ts   (22 passed)

Risk checklist

  • Cap size: thread-ownership 8 KiB (409 owner body is ~30 bytes).
  • User-visible behavior: Yes (intended) — a malformed/truncated 409 body now cancels the send (owner unknown) instead of throwing into the outer catch and allowing it; well-formed 409 unchanged.
  • Config/env/migration: No.
  • Security: Yes (improvement) — bounds the 409 body read, mitigating OOM from a compromised/buggy forwarder.
  • Plugins/providers/channels/SDK: Yes (bounded) — touches one extension; uses the shared readResponseTextLimited already used elsewhere.
  • Highest-risk area: a legitimate 409 body exceeding 8 KiB would fail to parse and cancel with owner=unknown — but a 409 owner field is tiny in practice, and the cancel behavior is the correct 409 semantics, so this is strictly safer than the prior crash/allow.
  • Mitigation: existing test suite passes (22 tests) + real local-forwarder runtime verification of well-formed / malformed / oversized 409 cancel semantics.

Closes: N/A (no existing issue)


Exact-head refresh

Rebased onto upstream/main 07284af7489d2b7a1e0a69b738968b707e21f0fb; current head is afa0f8fe32f61e493ba96c48082579f7d6f15c76.

The 409 path now uses the existing readProviderJsonResponse SDK contract with a 64 KiB cap rather than a local text-read/JSON.parse pair. A 409 remains authoritative: malformed or oversized response bodies log a bounded diagnostic and still return { cancel: true }, while a valid owner remains visible.

Node runtime evidence

Terminal capture of node --import tsx /tmp/verify-thread-ownership-real.mjs against the real exported plugin entrypoint, a real local forwarder, and unmocked globalThis.fetch:

Case 1 (well-formed 409): local server served request; cancel=true; owner=other-agent PASS
Case 2 (malformed 409): local server served request; cancel=true; owner=unknown PASS
Case 3 (70 KiB 409 vs 64 KiB cap): local server served request; cancel=true; owner=unknown PASS
Verdict: all cases PASS

Validation

node scripts/run-vitest.mjs run extensions/thread-ownership/index.test.ts  # 23 passed
node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json <three changed files>  # 0 errors
node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile /tmp/openclaw-98941-extensions.tsbuildinfo  # exit 0
node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile /tmp/openclaw-98941-extension-tests.tsbuildinfo  # exit 0
node scripts/build-all.mjs  # exit 0

Direct dependency contract reviewed: src/agents/provider-http-errors.ts:270 bounds readProviderJsonResponse with readResponseWithLimit, reports overflow with the supplied label, and normalizes malformed JSON. No config, migration, public SDK, or new dependency surface changed.

@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 14, 2026, 6:41 AM ET / 10:41 UTC.

Summary
Bounds Thread Ownership forwarder 409 JSON reads, preserves cancellation when conflict bodies are malformed or oversized, and adds focused regressions for those cases.

PR surface: Source +19, Tests +36. Total +55 across 3 files.

Reproducibility: yes. from source: current main parses the 409 body before cancellation, and malformed JSON reaches the outer fail-open catch. The contributor also supplied exact-head real-socket proof for the repaired valid, malformed, and oversized paths, though no separate current-main runtime capture was provided.

Review metrics: 1 noteworthy metric.

  • Delivery fallback: 1 behavior changed. Malformed or oversized ownership-conflict responses now cancel the Slack thread send instead of falling through the outer fail-open path.

Stored data model
Persistent data-model change detected: serialized state: extensions/thread-ownership/index.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: canonical
Canonical: #98941
Summary: This PR is the narrowest proof-positive candidate for the forwarder 409 root cause; the other Thread Ownership PRs either closed unmerged or combine the same repair with a distinct mention-cache change.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

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

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

Risk before merge

  • [P1] Merging this PR intentionally changes malformed or oversized forwarder 409 responses from fail-open delivery to cancelled delivery; that matches the original ownership contract, but it is still a message-delivery behavior change maintainers should acknowledge.
  • [P1] The overlapping fix(thread-ownership): bound forwarder conflict handling #101968 modifies the same conflict handler and also adds mention-cache policy, so landing both branches independently would create duplicate/conflicting work.

Maintainer options:

  1. Merge the focused invariant fix (recommended)
    Accept the intended fail-closed 409 behavior and land this narrower patch after confirming it is the chosen canonical conflict-handler change.
  2. Prefer the combined hardening branch
    Pause this PR and use the overlapping broader PR only if maintainers intentionally approve its additional mention-cache eviction policy.

Next step before merge

  • [P2] A maintainer should choose this focused branch or the overlapping broader branch as the single canonical landing path; no automated code repair is currently needed.

Maintainer decision needed

  • Question: Should maintainers land this narrowly scoped 409 cancellation fix, or use fix(thread-ownership): bound forwarder conflict handling #101968 as the canonical landing path because they also want its mention-cache cap?
  • Rationale: Both open PRs repair the same delivery invariant, while the competing branch adds a separate cache-retention policy; choosing the permanent scope is a maintainer ownership and merge-risk decision rather than a correctness defect in this patch.
  • Likely owner: steipete — He landed and corrected the original Thread Ownership feature and is the strongest history-backed owner for selecting the canonical follow-up scope.
  • Options:
    • Land the narrow repair (recommended): Merge this PR for the bounded authoritative-409 fix and reconcile or close the overlapping conflict-handler portion of the broader PR.
    • Use the broader repair: Keep fix(thread-ownership): bound forwarder conflict handling #101968 as canonical only if the 2000-entry mention-cache cap is also approved and its combined scope is preferred.

Security
Cleared: The patch reduces an existing response-body OOM exposure without adding dependencies, permissions, secret access, downloaded code, lifecycle hooks, or broader network capability.

Review details

Best possible solution:

Land exactly one authoritative-409 repair: prefer this narrower branch if the mention-cache retention policy is not part of the same decision, or select the broader pull request only if maintainers also want its separately proven cache cap.

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

Yes from source: current main parses the 409 body before cancellation, and malformed JSON reaches the outer fail-open catch. The contributor also supplied exact-head real-socket proof for the repaired valid, malformed, and oversized paths, though no separate current-main runtime capture was provided.

Is this the best way to solve the issue?

Yes. Reusing the existing bounded SDK JSON reader inside the authoritative 409 branch is the narrowest maintainable fix and preserves the feature’s original cancellation contract; the only unresolved question is whether maintainers also want the competing PR’s separate cache cap.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 09e5d60d3c5c.

Label changes

Label changes:

  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P2: This is a focused reliability and resource-bound fix for an optional multi-agent Slack plugin with limited blast radius.
  • merge-risk: 🚨 message-delivery: The patch intentionally changes unreadable 409 conflict handling from allowing a send to cancelling it, directly affecting outbound message suppression.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): Exact-head terminal evidence exercises the exported plugin hook against a real local HTTP forwarder with unmocked fetch and observes cancellation for valid, malformed, and oversized 409 responses.
  • proof: sufficient: Contributor real behavior proof is sufficient. Exact-head terminal evidence exercises the exported plugin hook against a real local HTTP forwarder with unmocked fetch and observes cancellation for valid, malformed, and oversized 409 responses.
Evidence reviewed

PR surface:

Source +19, Tests +36. Total +55 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 2 21 2 +19
Tests 1 36 0 +36
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 57 2 +55

What I checked:

Likely related people:

  • DarlingtonDeveloper: Authored the original Thread Ownership plugin and established the forwarder 409 cancellation contract in the feature later landed as commit 51296e7. (role: introduced behavior; confidence: high; commits: 5f6c15cfb766, 51296e770c70; files: extensions/thread-ownership/index.ts, extensions/thread-ownership/index.test.ts)
  • steipete: Carried the original feature through its typing/config corrections and landed the canonical Thread Ownership implementation on main. (role: merger and adjacent owner; confidence: high; commits: 72873b80405f, 9711521, 02fecd058024; files: extensions/thread-ownership/index.ts, extensions/thread-ownership/index.test.ts, src/channels/plugins/outbound/slack.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: 🧂 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. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jul 2, 2026
@miorbnli

miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed all P1 feedback:

  • thread-ownership 409 cancel semantics: wrapped parse in try/catch so a truncated/malformed 409 body still cancels (owner=undefined, logged as unknown)
  • regression test in thread-ownership/index.test.ts: cancels when the 409 conflict body is truncated or malformed (22 tests pass)
  • per-site runtime proof for all three patched functions via verify-bounded-per-site.mjs (thread-ownership 409, discord webhook, huggingface model discovery; each exercises the real readResponseTextLimited + the site's fallback logic)

@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

Addressed all three P1 items:

  1. [P1] HuggingFace cap: raised 64KB -> 256KB (real /v1/models response ~150KB; 256KB gives generous headroom while still bounding a compromised endpoint) — commit 162c068
  2. [P1] String wrapper: removed redundant String() on owner ?? "unknown" (already returns a string) — commit 162c068
  3. [P1] Per-site changed-path proof:
    • thread-ownership: vitest (22 tests pass, incl. "cancels when the 409 conflict body is truncated or malformed" — drives the real message_sending hook + mocked forwarder, exercising the actual try/catch cancel-semantics fix)
    • discord webhook: real readResponseTextLimited (the actual helper now called by webhook) + well-formed/non-JSON parse+fallback, proven live in this session
    • huggingface: real readResponseTextLimited (the actual helper now called by model discovery) + well-formed/oversize parse+fallback, proven live in this session

@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 rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jul 2, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 2, 2026
@miorbnli

miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated PR body with explicit per-site proof (current-head commit 162c068):

  • thread-ownership 409 (8KB cap): truncated body still cancels (409 status semantics), regression test in index.test.ts
  • discord webhook (8KB cap): non-JSON falls back to {} (preserves prior .catch tolerance)
  • huggingface (256KB cap): oversize falls back to bundled catalog (matches !response.ok branch)
    Cap sizes documented in Risk checklist: 8KB for thread-ownership/webhook (tiny payloads), 256KB for HF (real /v1/models ~150KB, generous headroom).

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 2, 2026
@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: 🦪 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. labels Jul 2, 2026
@miorbnli

miorbnli commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated body with real per-function proof:

  • Part 1: thread-ownership — real register(api) + real message_sending hook + real HTTP server (truncated 409 -> cancel=true, log shows "owned by unknown"; well-formed 409 -> cancel=true, log shows "owned by other-agent") — exercises the actual changed code path in index.ts
  • Part 2: discord webhook — real readResponseTextLimited (the exact helper now called by send.webhook.ts) + parse+fallback
  • Part 3: huggingface — real readResponseTextLimited (the exact helper now called by models.ts) + parse+fallback
    This is no longer helper-level proof; thread-ownership drives the real production entry point through register(api).

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label Jul 2, 2026
@clawsweeper clawsweeper Bot removed the status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. label Jul 2, 2026
@miorbnli miorbnli changed the title fix(extensions): bound success-path JSON response reads fix(thread-ownership): bound 409 response read and preserve cancel semantics Jul 8, 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: 🐚 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. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 8, 2026
@miorbnli

miorbnli commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Replaced mock-only Vitest proof with real local-forwarder runtime verification: a real HTTP server stands in for the Slack forwarder and the real exported plugin entrypoint (register + message_sending hook) is driven over real sockets (globalThis.fetch NOT mocked). All 3 cases PASS (well-formed/malformed/oversized 409 -> cancel=true). See updated Real behavior proof (commit 47c0395).

@clawsweeper

clawsweeper Bot commented Jul 8, 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: 🦪 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. labels Jul 8, 2026
miorbnli and others added 2 commits July 14, 2026 11:58
…mantics

The thread-ownership 409 conflict path still used unbounded resp.json(),
inconsistent with the bounded readers shipped across the other channel/provider
success paths. A compromised or buggy forwarder returning a multi-GB 409 body
would buffer the entire body before JSON.parse, OOMing the agent.

Bound the 409 body via readResponseTextLimited (8 KiB cap; a 409 owner field is
~30 bytes). Wrap the parse in try/catch so a truncated or malformed 409 body
still cancels the send (owner logged as "unknown") — the 409 status itself
means another agent owns this thread, independent of body parseability.

The discord webhook and huggingface /v1/models success reads from the original
PR were superseded upstream by openclaw#98098 and openclaw#101079 (readProviderJsonResponse),
so this branch now carries only the thread-ownership fix.

Co-Authored-By: Claude <[email protected]>
@miorbnli
miorbnli force-pushed the fix/unbounded-success-json-reads branch from 47c0395 to afa0f8f Compare July 14, 2026 04:11
@miorbnli

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Exact-head refresh at afa0f8fe32f61e493ba96c48082579f7d6f15c76, rebased onto upstream/main 07284af7489d2b7a1e0a69b738968b707e21f0fb:

  • replaced the local bounded-text + JSON.parse pair with the existing readProviderJsonResponse SDK helper (64 KiB cap);
  • preserves the authoritative 409 => { cancel: true } invariant for malformed and oversized bodies, with bounded warning diagnostics and owner=unknown fallback;
  • adds separate malformed and 70 KiB-over-64 KiB focused regressions; 23 focused tests pass;
  • real local-forwarder Node proof drives the exported plugin entrypoint with unmocked fetch for valid, malformed, and oversized 409 bodies; all pass;
  • format, oxlint, extension prod/test tsgo, and full build pass.

This intentionally does not copy unrelated mention-cache hardening from #101968. Please re-review and re-rate this exact head, including whether this branch can stand independently rather than remain superseded.

@clawsweeper

clawsweeper Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 14, 2026
@steipete steipete self-assigned this Jul 18, 2026
@steipete
steipete merged commit 9d0836e into openclaw:main Jul 18, 2026
192 of 197 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 19, 2026
…mantics (openclaw#98941)

* fix(thread-ownership): bound 409 response read and preserve cancel semantics

The thread-ownership 409 conflict path still used unbounded resp.json(),
inconsistent with the bounded readers shipped across the other channel/provider
success paths. A compromised or buggy forwarder returning a multi-GB 409 body
would buffer the entire body before JSON.parse, OOMing the agent.

Bound the 409 body via readResponseTextLimited (8 KiB cap; a 409 owner field is
~30 bytes). Wrap the parse in try/catch so a truncated or malformed 409 body
still cancels the send (owner logged as "unknown") — the 409 status itself
means another agent owns this thread, independent of body parseability.

The discord webhook and huggingface /v1/models success reads from the original
PR were superseded upstream by openclaw#98098 and openclaw#101079 (readProviderJsonResponse),
so this branch now carries only the thread-ownership fix.

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

* fix(thread-ownership): bound forwarder conflict handling

---------

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: thread-ownership merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor 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