Skip to content

fix(agents): guard delivery-evidence attachment recursion against cycles#97041

Merged
vincentkoc merged 3 commits into
openclaw:mainfrom
Pick-cat:fix/delivery-evidence-attachment-cycle
Jun 27, 2026
Merged

fix(agents): guard delivery-evidence attachment recursion against cycles#97041
vincentkoc merged 3 commits into
openclaw:mainfrom
Pick-cat:fix/delivery-evidence-attachment-cycle

Conversation

@Pick-cat

@Pick-cat Pick-cat commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

collectMediaUrlsFromRecord in src/agents/embedded-agent-runner/delivery-evidence.ts recurses through record.attachments[].attachments[] with no cycle guard. Delivery-evidence payloads arrive as in-process unknown objects (AgentDeliveryEvidence.payloads?: unknown, produced by dispatchGatewayMethodInProcess — not JSON-deserialized), so a malformed self-referential attachments chain recurses until the stack overflows (RangeError: Maximum call stack size exceeded), crashing delivery-evidence collection on the subagent announce-delivery path.

Why This Change Was Made

Add a WeakSet<object> visited-set to bound the descent — the same pattern already used for in-process runtime payloads by redactStringsDeep in src/agents/embedded-agent-runner/embedded-agent-subscribe.tools.ts. The guard is threaded through the internal recursion only; external entry points start a fresh set, so behavior for normal (acyclic) payloads is unchanged. No new abstraction, no API change — the new seen parameter is optional and defaults to a fresh WeakSet.

Scope: this is a defensive bound at an existing malformed-input boundary (the function is already written to tolerate arbitrary unknown payloads with typeof/Array.isArray guards). It does not change which URLs are collected for well-formed payloads.

User Impact

A malformed/cyclic attachments payload can no longer crash subagent announce-delivery evidence collection with a stack overflow; the collector returns the URLs it can reach and stops at the cycle.

Evidence

New focused test src/agents/embedded-agent-runner/delivery-evidence.test.ts drives the real exported collectDeliveredMediaUrls.

With the fix — node scripts/run-vitest.mjs src/agents/embedded-agent-runner/delivery-evidence.test.ts --run:

 Test Files  1 passed (1)
      Tests  3 passed (3)

Cases: nested (acyclic) attachments still collect every URL; a self-referential cycle (att.attachments = [att]) and a mutual cycle (a.attachments=[b]; b.attachments=[a]) both return gracefully.

Negative control — revert only delivery-evidence.ts to main, keep the test:

 × does not overflow the stack on a self-referential attachments cycle
 × does not overflow on a mutual attachments cycle
   AssertionError: expected [Function] to not throw an error but
   'RangeError: Maximum call stack size exceeded' was thrown
 Tests  2 failed | 1 passed (3)

This proves the bug is real and the test guards it; the acyclic case stays green, confirming the fix does not change normal collection.

No regression — consumers of collectDeliveredMediaUrls:

$ node scripts/run-vitest.mjs src/agents/subagent-announce-delivery.test.ts --run
 Test Files  1 passed (1)
      Tests  103 passed (103)

oxlint clean on both changed files. Production delta is small: delivery-evidence.ts +14/-2.

Real affected-path proof — runnable harness driving the exported announce-delivery functions

Beyond the unit test, a harness drives the actual functions the subagent announce-delivery path uses (getGatewayAgentResultcollectDeliveredMediaUrls and hasVisibleAgentPayload) against an in-process response whose attachments form a cycle. The response is shaped like a real dispatchGatewayMethodInProcess result (agent payload nested under .result), i.e. an in-process runtime object — not JSON — so the cycle is structurally representable.

# WITH FIX (PR head)
$ node --import tsx de_harness.mjs
getGatewayAgentResult -> evidence extracted: true
collectDeliveredMediaUrls -> ["https://example.com/report.png"]
hasVisibleAgentPayload -> true
AFFECTED-PATH SURVIVED THE CYCLE

# NEGATIVE CONTROL — delivery-evidence.ts reverted to origin/main (unguarded)
$ node --import tsx de_harness.mjs
getGatewayAgentResult -> evidence extracted: true
RangeError: Maximum call stack size exceeded

Harness payload:

const cyclicPayload = { url: "https://example.com/report.png", text: "done" };
cyclicPayload.attachments = [cyclicPayload];
const inProcessResponse = { result: { payloads: [cyclicPayload] } };
getGatewayAgentResult(inProcessResponse); // real extraction
collectDeliveredMediaUrls(evidence);      // real collection used by the announce path
hasVisibleAgentPayload(evidence);         // also recurses into attachments

This shows the cycle crashes the real announce-delivery collection on main (not just the leaf helper), and that the fix lets the whole path complete.

Not tested here

I did not reproduce a cyclic payload from a live model agent run — I have not found a production path that constructs one today, and a cyclic attachments object does not arise from a normal run. This hardens the in-process unknown payload boundary against the crash, consistent with the existing redactStringsDeep cycle guard in the same subsystem; the harness above exercises the real affected-path exported functions with the cyclic payload injected.

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Jun 26, 2026
@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution. I reviewed the branch, and this PR is not a good landing base for OpenClaw.

Close: the original two-file delivery-evidence fix is useful, but the current branch is no longer a safe landing candidate because two later commits expanded it into a 222-file PR spanning unrelated release, CI, mobile, docs, generated locale, state/session, voice-call, memory-core, and provider/runtime work.

So I’m closing this PR rather than keeping an unmergeable branch open. A new narrow PR that carries only the useful part is welcome.

Review details

Best possible solution:

Preserve the initial delivery-evidence guard as a narrow two-file PR or reset this branch to that commit, and move unrelated release/runtime/generated work into separate owner-reviewed branches.

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

Yes, by source inspection and provided negative-control proof. Current main recurses through nested attachment records without a visited guard, and the PR body shows a cyclic in-process payload causing a RangeError before the focused fix.

Is this the best way to solve the issue?

No for the current branch. The initial two-file WeakSet guard is the right narrow fix, but the current PR is not the best way to land it because unrelated broad changes now dominate the merge result.

Security review:

Security review needs attention: The current head changes automation, release, mobile, generated, and runtime surfaces outside the stated agent fix, so it is not cleared as a supply-chain or release-safety merge candidate.

  • [medium] Unrelated automation and release surfaces — .github/workflows/ci.yml:861
    Changing CI runner parallelism and release-adjacent files in a narrow agent bug-fix PR makes the merge result security- and supply-chain-sensitive without matching review or proof.
    Confidence: 0.9

AGENTS.md: found and applied where relevant.

What I checked:

  • Current live PR scope: Live PR metadata reports head 0416619 with 222 changed files, +16396/-909, and labels across docs, multiple channels, mobile apps, gateway, CLI, scripts, agents, Codex, and memory-core. (041661997e1e)
  • Useful focused initial fix: The first commit is the focused useful change: 2 files, +60/-2, limited to delivery-evidence runtime code and its regression test. (3c6b46cb56f9)
  • Current main recursion path: Current main recursively descends nested attachment records without a visited set, matching the reported stack-overflow risk for cyclic in-process payloads. (src/agents/embedded-agent-runner/delivery-evidence.ts:83, 5880e0afc4dc)
  • Focused fix implementation: The initial fix adds a WeakSet visited guard and passes it through recursive attachment traversal. (src/agents/embedded-agent-runner/delivery-evidence.ts:83, 3c6b46cb56f9)
  • Focused regression coverage: The initial test covers nested attachments, a self-referential cycle, and a mutual attachment cycle through collectDeliveredMediaUrls. (src/agents/embedded-agent-runner/delivery-evidence.test.ts:24, 3c6b46cb56f9)
  • Unrelated follow-on commits: The next two commits are broad unrelated work: d3e19ab touches 175 files and 0416619 touches 46 files, including release, CI, mobile, generated locale, state/session, voice-call, provider, and memory-core surfaces. (041661997e1e)

Likely related people:

  • ly-wang19: Git blame and PR metadata show perf(update): reuse missing plugin payload id set #96950 introduced the current delivery-evidence and subagent announce-delivery implementation that contains the recursive attachment traversal. (role: introduced current behavior; confidence: high; commits: a0e9ca1e9544, c5b1ed98f24a; files: src/agents/embedded-agent-runner/delivery-evidence.ts, src/agents/subagent-announce-delivery.ts)
  • vincentkoc: GitHub metadata shows Vincent merged the current-main delivery-evidence PR and authored the two broad follow-on commits that made this branch unmergeably dirty. (role: merger and recent branch contributor; confidence: high; commits: f57c61a311e3, d3e19ab9bea8, 041661997e1e; files: src/agents/embedded-agent-runner/delivery-evidence.ts, .github/workflows/ci.yml, CHANGELOG.md)

Codex review notes: model internal, reasoning high; reviewed against 5880e0afc4dc.

@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 Jun 26, 2026
@Pick-cat

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added a real affected-path proof to the PR body Evidence (no code change).

Beyond the unit test, a runnable harness now drives the actual functions the subagent announce-delivery path uses — getGatewayAgentResultcollectDeliveredMediaUrls and hasVisibleAgentPayload — against an in-process response shaped like a real dispatchGatewayMethodInProcess result (agent payload nested under .result, an in-process runtime object, not JSON) whose attachments form a cycle.

# WITH FIX:            collectDeliveredMediaUrls -> ["https://example.com/report.png"]; AFFECTED-PATH SURVIVED THE CYCLE
# NEGATIVE CONTROL (delivery-evidence.ts reverted to main): RangeError: Maximum call stack size exceeded

This exercises the real affected-path exported functions (not just the leaf helper) and shows the cycle crashes the announce-delivery collection on main while the fix lets the whole path complete. Patch quality was already rated 🦞 diamond lobster; this raises the proof beyond test-only. A live-model run cannot naturally produce a cyclic attachments payload, so the cycle is injected into the real in-process response — noted honestly under "Not tested here".

@clawsweeper

clawsweeper Bot commented Jun 26, 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: 🦞 diamond lobster Very strong PR readiness with only minor 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 Jun 26, 2026
@vincentkoc
vincentkoc requested a review from a team as a code owner June 27, 2026 00:28
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: googlechat Channel integration: googlechat channel: matrix Channel integration: matrix app: android App: android app: ios App: ios app: web-ui App: web-ui extensions: memory-core Extension: memory-core cli CLI command changes scripts Repository scripts commands Command implementations channel: feishu Channel integration: feishu extensions: codex size: XL and removed size: S labels Jun 27, 2026
@openclaw-barnacle openclaw-barnacle Bot added channel: voice-call Channel integration: voice-call gateway Gateway runtime labels Jun 27, 2026
@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. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 automation 🚨 May affect CI, automerge, proof capture, label sync, or maintainer automation. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 27, 2026
@clawsweeper

clawsweeper Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jun 27, 2026
@vincentkoc
vincentkoc merged commit 4985671 into openclaw:main Jun 27, 2026
151 of 160 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
…les (openclaw#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
…les (openclaw#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
xydigit-zt pushed a commit to xydigit-zt/xydigit-zt-openclaw that referenced this pull request Jun 28, 2026
…les (openclaw#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…les (openclaw#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…les (openclaw#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…les (openclaw#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
RomneyDa added a commit that referenced this pull request Jul 22, 2026
* fix: gate diagnostics command to owners

(cherry picked from commit 170bf72)

* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup (#96100)

* fix(agent): wait for retained session write before releasing held lock on abort

* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup

* fix(test): reject fallback acquire with SessionWriteLockTimeoutError in active-scope cleanup test

* fix(agent): trim retained-lock comments

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>
(cherry picked from commit 0a042f6)

* fix(gateway): resume channel after pending task recovery

(cherry picked from commit 6039da3)

* fix(gateway): resume channel after pending task recovery

(cherry picked from commit ecd29fe)

* fix(outbound): ignore empty delivery receipts (#79811)

(cherry picked from commit 9a735be)

* fix(agents): guard delivery-evidence attachment recursion against cycles (#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 4985671)

* fix(opencode-go): re-arm idle timer on block-boundary events to prevent false stalled-stream abort (#97128)

* fix(opencode-go): re-arm idle timer on block-boundary events to prevent false stalled-stream abort

When the opencode-go model finalizes a tool call and deliberates before
the next one, the provider emits real block-boundary SSE events
(text_end, thinking_end, toolcall_start, toolcall_end) that prove the
socket is alive, but the watchdog's isProviderProgressEvent only
returned true for token deltas (text_delta, thinking_delta,
toolcall_delta). This caused the idle timer to fire and falsely abort a
live stream, replacing a completed answer with a stalled error and
dropping the provider's real done event.

Fix: include block-boundary events in isProviderProgressEvent so the
idle timer is re-armed on any forward-progress provider event.
text_start and thinking_start are intentionally excluded because they
are synthetic preamble events that should not shorten the first-event
window.

Closes #96518

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

* test(opencode-go): satisfy lint in stream regression

* test(opencode-go): satisfy lint in stream regression

* test(opencode-go): satisfy lint in stream regression

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 552ec2b)

* fix(model-fallback): don't rethrow provider-side AbortErrors as user cancellations (#90908)

* fix(model-fallback): don't rethrow provider-side AbortErrors as user cancellations

When the LLM API closes the connection mid-stream, the fetch layer
surfaces AbortError("This operation was aborted") with no external
abort signal triggered. The old guard `shouldRethrowAbort()` returned
false for these errors (because isTimeoutError matched the message),
so they fell through to the fallback loop but were never retried —
the error propagated up and produced SILENT_REPLY_TOKEN in group
sessions, permanently silencing the topic.

Replace the guard with a direct check: only rethrow AbortError when
the external abort signal is actually set (user/gateway cancellation).
Provider-side AbortErrors without an external signal now fall through
to the next fallback candidate, giving the system a chance to recover.

* fix(cron): forward abort signal into runWithModelFallback

Thread the cron executor's abort signal into the shared
runWithModelFallback call so that cron timeouts and cancellations
stop the fallback chain instead of retrying with the next candidate.

Previously, the run callback checked params.abortSignal?.aborted and
threw, but runWithModelFallback itself had no signal — so the new
guard in model-fallback.ts could not distinguish a caller abort from
a provider-side AbortError and would retry silently.

Also adds a focused regression test verifying the signal is forwarded.

---------

Co-authored-by: Shengting Xie <[email protected]>
Co-authored-by: yayu <[email protected]>
(cherry picked from commit 98ed83f)

* fix(browser): block node routes when sandbox host control is disabled (#97958)

(cherry picked from commit 2cf765f)

* fix(exec): bind Windows allowlist execution path (#98260)

* fix(exec): bind windows allowlist execution path

* fix(exec): add windows shadow execution proof

* fix(exec): preserve wildcard allowlist behavior

* fix(exec): correct blocked plan test fixture

(cherry picked from commit 3811001)

* fix(mcp): suppress unhandled error on stderr pipe in stdio transport (#99803)

* fix(mcp): suppress unhandled error on stderr pipe in stdio transport

When child.stderr is piped to stderrStream without an error
handler, a stream-level error (EPIPE, I/O failure) crashes the
process. Add a noop error handler before the pipe, consistent
with the error handlers already present on stdin and stdout.

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

* test(mcp): add regression test for stderr pipe error suppression

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

* fix(mcp): report stderr stream errors

* fix(mcp): report stderr stream errors

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 1b84316)

* Harden macOS SQLite WAL checkpoints (#99067)

(cherry picked from commit f7f1be2)

* fix(secrets): suppress unhandled stdout/stderr stream errors in exec resolver (#100521)

* fix(secrets): suppress unhandled stdout/stderr stream errors in exec resolver

* proof(secrets): add real behavior proof script for exec resolver stream error catch

* proof(secrets): replace wrapper with real exec resolver stream error proof

* style: apply oxfmt to changed files

(cherry picked from commit c9a0783)

* fix(agents): retry transient filesystem races when reading workspace bootstrap files (#100910)

* fix(agents): retry transient filesystem races when reading workspace bootstrap files

* fix(agents): retry transient boundary resolution

---------

Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit f36d170)

* fix(gateway): finish plugin HTTP responses after post-header failures (#102125)

* fix(gateway): finish plugin HTTP responses after post-header failures

* test(gateway): satisfy plugin HTTP regression lint

* fix(gateway): skip ending destroyed plugin responses

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 240d350)

* fix(gateway): validate exact custom browser origins (#38290)

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit fa0349a)

* fix: block unspecified trusted DNS targets (#103075)

(cherry picked from commit c70f3d0)

* fix(channels): make nack callbacks idempotent (#104919)

* fix(channels): make nack callbacks idempotent

* fix(channels): coalesce overlapping nack callbacks

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 02d307e)

* fix(channels): prevent base URL credentials in status output (#107754)

* fix(channels): redact credentials in account URLs

* fix(channels): sanitize final status summaries

(cherry picked from commit 210340f)

* fix(channels): prevent lifecycle listener buildup (#109108)

(cherry picked from commit 0e1fad7)

* fix(sandbox): use Buffer.byteLength for env var value size limit (#105017)

* fix(sandbox): use Buffer.byteLength for env var value size limit

validateEnvVarValue checked value.length (UTF-16 code units) against
the 32768-byte limit, so multi-byte CJK values like "值".repeat(11000)
passed the check despite exceeding 33 KB in UTF-8. Switch to
Buffer.byteLength(value, "utf8") so the limit matches the actual byte
count the OS and child processes see.

* test(sandbox): simplify env byte-limit coverage

Co-authored-by: 唐梓夷0668001293 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 84fb48c)

* fix(gateway): guard process.kill ESRCH race in signalVerifiedGatewayPidSync (#109590)

* fix(gateway): guard process.kill ESRCH race in signalVerifiedGatewayPidSync

A verified gateway process can exit between the argv validation check and
the process.kill call, causing an unhandled ESRCH error. Wrap the kill in
try-catch and silently swallow ESRCH (process already gone = signal
already delivered).

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

* docs(gateway): explain ESRCH signal race

Co-authored-by: 丁宇婷0668001435 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 853b1a8)

* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS SSRF bypass (#110693)

* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS bypass

The isAutoAllowedLitellmHostname helper auto-enables private-network access
for loopback-style hosts. Before this fix, lowered.startsWith("127.")
matched DNS hostnames like 127.evil.com, letting remote endpoints bypass
the explicit allowPrivateNetwork opt-in — a SSRF risk.

Add isIP(host)===4 guard so only literal IPv4 loopback addresses qualify.
Same canonical pattern as extensions/slack/src/monitor/relay-source.ts:271
and the codex loopback fix.

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

* test(litellm): cover loopback endpoint policy

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 3d03b60)

* fix(discord): sustained gateway bursts stop growing memory (#110954)

* fix(discord): sustained gateway bursts stop growing memory

* fix(discord): contain gateway queue overflow

* fix(discord): drop oldest saturated gateway sends

Co-authored-by: 张贵萍0668001030 <[email protected]>

* fix(discord): surface gateway overflow warnings

Co-authored-by: 张贵萍0668001030 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 69aeba9)

* fix(gateway): bound busy channel health by real run age (#103793)

* fix(gateway): bound busy channel health by real run age

The channel health policy treats a channel as healthy-busy even while
disconnected, bounded only by a 25 minute stale ceiling measured from
lastRunActivityAt. The run-state heartbeat refreshes lastRunActivityAt
every 60 seconds for as long as any run is active, so a run that hangs
forever (for example a send blocking on a dead socket after the
transport already reported connected:false) keeps that timestamp fresh
and the stuck ceiling is never reached. The account is then reported
healthy forever by the health monitor, readiness probe, and health CLI,
and no restart ever fires.

createRunStateMachine now tracks each in-flight run's start time keyed by
an opaque run handle and publishes the oldest still-active run's start as
activeRunStartedAt. The health policy busy override keys its ceiling off
the real run age, so a run stuck longer than the threshold reports stuck
and the monitor can restart it. Because the reported start is the oldest
active run and advances to the next-oldest as runs complete, a channel
churning through many short overlapping runs (activeRuns above 1 across
concurrent queue keys) stays healthy; only a genuinely hung run breaches
the ceiling. Short and active runs stay healthy and the existing
lastRunActivityAt fallback is preserved for snapshots without a start
time.

* fix(channels): retain run-state callback compatibility

Keep the released zero-argument onRunEnd callback source-compatible while allowing internal queue callers to pass a run handle for exact concurrent-run accounting. The compatibility path closes the oldest active run, preserving existing lifecycle behavior for consumers that do not use handles.

* fix(channels): keep anonymous runs out of age tracking

The zero-argument lifecycle callbacks cannot identify which concurrent run completed, so they must not update the identity-sensitive run start used by channel health. Keep their busy count separately and reserve exact start tracking for the shared queue's handle-aware lifecycle path.

* fix(channels): keep tracked runs internal

Keep the public run-state lifecycle callbacks unchanged. The channel queue now owns opaque run identity and augments its status updates with the oldest active queue run, so implementation details do not expand the SDK surface.

* fix(channels): type queue run start status

Keep activeRunStartedAt in the internal status patch type so the queue can publish its private tracked-run age through the existing status sink.

* fix(channels): wrap isActive to satisfy unbound-method lint

* fix(gateway): gate busy run-age ceiling on disconnected transport

(cherry picked from commit 18b79d9)

* fix(deps): update fast-uri past advisory

(cherry picked from commit 1be9db0)

* fix(release): adapt maintenance-line hardening

Backport/adapt 18ec9ce, dea1fe1, 7f32b6c, 1da345e, 931ac3e, 89780d5, and c0d99ed for the 2026.6 extended-stable maintenance line.

* fix(deps): bump protobufjs to 7.6.5

Backport-adapted from a230f74.

* test(gateway): cover bounded macOS process probe

* chore(release): prepare 2026.6.34

* test(dotenv): share path override environment assertions

* fix(release): resolve 2026.6.34 CI blockers

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: joshavant <[email protected]>
Co-authored-by: Peter Lee <[email protected]>
Co-authored-by: sallyom <[email protected]>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Liu Wenyu <[email protected]>
Co-authored-by: pick-cat <[email protected]>
Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: weiqinl <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: shengting <[email protected]>
Co-authored-by: Shengting Xie <[email protected]>
Co-authored-by: yayu <[email protected]>
Co-authored-by: Agustin Rivera <[email protected]>
Co-authored-by: cxbAsDev <[email protected]>
Co-authored-by: ooiuuii <[email protected]>
Co-authored-by: Masato Hoshino <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: mushuiyu886 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
Co-authored-by: Bruno Wowk (Volky) <[email protected]>
Co-authored-by: Pavan Kumar Gondhi <[email protected]>
Co-authored-by: Glucksberg <[email protected]>
Co-authored-by: xingzhou <[email protected]>
Co-authored-by: tzy-17 <[email protected]>
Co-authored-by: krissding <[email protected]>
Co-authored-by: lsr911 <[email protected]>
Co-authored-by: Yuval Dinodia <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: android App: android app: ios App: ios app: web-ui App: web-ui channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: matrix Channel integration: matrix channel: voice-call Channel integration: voice-call cli CLI command changes commands Command implementations docs Improvements or additions to documentation extensions: codex extensions: memory-core Extension: memory-core gateway Gateway runtime merge-risk: 🚨 automation 🚨 May affect CI, automerge, proof capture, label sync, or maintainer automation. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. scripts Repository scripts size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants