Skip to content

fix(cron): avoid delivered status for empty outbound receipts#79811

Merged
velvet-shark merged 1 commit into
openclaw:mainfrom
indulgeback:codex/cron-announce-not-delivered-regression
Jun 26, 2026
Merged

fix(cron): avoid delivered status for empty outbound receipts#79811
velvet-shark merged 1 commit into
openclaw:mainfrom
indulgeback:codex/cron-announce-not-delivered-regression

Conversation

@indulgeback

@indulgeback indulgeback commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #79753.

Cron announce/direct delivery could still treat an outbound adapter result with no platform delivery identity as a successful send on text/media paths. That lets a run show delivered: true even though the channel adapter produced no usable message receipt.

This tightens the shared outbound delivery result handling so only results with a real delivery identity (messageId, chatId, channelId, roomId, conversationId, toJid, or pollId) are counted as sent. Empty/identity-less results now flow through the existing suppressed/no-visible-result path, so cron records delivered: false instead of a false positive.

Real behavior proof

  • Behavior addressed: A cron announce/direct delivery path must not report a channel delivery as successful when the outbound channel adapter returns no platform message identity.
  • Real environment tested: Local OpenClaw checkout on macOS, branch codex/cron-announce-not-delivered-regression, Node v25.7.0, using the patched shared outbound delivery runtime through sendDurableMessageBatch.
  • Exact steps or command run after this patch: Ran a local OpenClaw runtime smoke command that registers a Telegram outbound adapter returning { channel: "telegram", messageId: "" }, then calls sendDurableMessageBatch with skipQueue: true.
  • Evidence after fix: Terminal output from the patched checkout:
$ pnpm exec tsx -e '(async () => { const { sendDurableMessageBatch } = await import("./src/channels/message/send.ts"); const { setActivePluginRegistry } = await import("./src/plugins/runtime.ts"); const { createOutboundTestPlugin, createTestRegistry } = await import("./src/test-utils/channel-plugins.ts"); const sendText = async () => ({ channel: "telegram", messageId: "" }); setActivePluginRegistry(createTestRegistry([{ pluginId: "telegram", source: "local-smoke", plugin: createOutboundTestPlugin({ id: "telegram", outbound: { deliveryMode: "direct", sendText } }) }])); const result = await sendDurableMessageBatch({ cfg: {}, channel: "telegram", to: "123", payloads: [{ text: "cron receipt smoke" }], skipQueue: true }); console.log(JSON.stringify(result, null, 2)); })();'
{
  "status": "suppressed",
  "results": [],
  "receipt": {
    "platformMessageIds": [],
    "parts": [],
    "sentAt": 1778397010778,
    "raw": []
  },
  "reason": "adapter_returned_no_identity",
  "payloadOutcomes": [
    {
      "index": 0,
      "status": "suppressed",
      "reason": "adapter_returned_no_identity"
    }
  ]
}
  • Observed result after fix: The outbound runtime suppressed the empty platform receipt (status: "suppressed", results: [], reason: "adapter_returned_no_identity") instead of returning a sent result. The cron regression test on the same branch verifies this maps to delivered: false with deliveryAttempted: true.
  • What was not tested: I did not run a live WeChat or Feishu cron announce job locally because I do not have those channel credentials in this environment.

Tests

  • pnpm test src/cron/isolated-agent.direct-delivery-core-channels.test.ts
  • pnpm test src/channels/message/send.test.ts src/infra/outbound/deliver.test.ts
  • pnpm test src/cron/isolated-agent.direct-delivery-core-channels.test.ts src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/channels/message/send.test.ts src/infra/outbound/deliver.test.ts
  • pnpm test src/cron/isolated-agent.direct-delivery-core-channels.test.ts src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/channels/message/send.test.ts src/infra/outbound/deliver.test.ts extensions/telegram/src/outbound-adapter.test.ts extensions/feishu/src/outbound.test.ts extensions/slack/src/outbound-delivery.test.ts
  • pnpm exec oxfmt --check --threads=1 src/infra/outbound/deliver.ts src/cron/isolated-agent.direct-delivery-core-channels.test.ts
  • git diff --check

@openclaw-barnacle openclaw-barnacle Bot added size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 81868a92b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1624 to 1630
...filterIdentifiedDeliveryResults(
await handler.sendFormattedText(
payloadSummary.text,
applySendReplyToConsumption(sendOverrides),
),
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid emitting stale messageId after filtering no-identity sends

After this change filters identity-less results, a payload can now produce zero new results entries even though earlier payloads already populated results. In that case, the later emitMessageSent still takes messageId from results.at(-1), which points to a previous payload’s message ID. This misattributes a failed/suppressed send to the wrong platform message whenever multiple payloads are delivered in one call and a later text send returns only identity-less results.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 9:33 PM ET / 01:33 UTC.

Summary
The PR filters identity-less outbound delivery results from text, formatted-text/fallback, and media paths and adds cron/outbound regression coverage.

PR surface: Source +20, Tests +97. Total +117 across 3 files.

Reproducibility: yes. at source level. Current main and v2026.6.9 can still turn identity-less outbound adapter results into non-empty delivered results; I did not run live WeChat or Feishu credentials in this read-only review.

Review metrics: 1 noteworthy metric.

  • Shared result collection paths: 3 tightened: text, formatted/fallback text, media. These paths decide whether durable sends and cron announce delivery report sent or suppressed across channels.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #79753
Summary: This PR is the active fix candidate for the linked cron announce false-delivered issue; narrower media-only attempts are closed, and a broader maintainer issue owns cross-channel delivery semantics.

Members:

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

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

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

Rank-up moves:

  • none.

Risk before merge

  • [P1] Merging intentionally changes shared outbound accounting: adapters that return empty or identity-less receipts can now produce suppressed or not-delivered outcomes instead of delivered.
  • [P1] The supplied real behavior proof exercises the shared runtime with a local Telegram-shaped adapter, not live WeChat or Feishu cron delivery from the linked report.
  • [P2] The broader durable final fallback contract remains open in Define durable final fallback delivery semantics across channels #87561, so this PR should land only as the narrow empty-receipt accounting slice.

Maintainer options:

  1. Accept strict empty-receipt accounting (recommended)
    Land after maintainers agree that empty or identity-less adapter results should count as suppressed or not delivered across shared outbound paths.
  2. Audit high-use adapter shapes first
    Pause merge long enough to inspect bundled and high-use external adapters if maintainers are unsure whether any valid delivery path intentionally returns no identity.
  3. Defer to the broader contract
    Keep this PR open or close it in favor of the broader durable delivery issue if maintainers want one coordinated cross-channel contract change first.

Next step before merge

  • [P2] No narrow automation repair remains; maintainers need to accept or defer the stricter shared receipt semantics before merge.

Security
Cleared: The diff is limited to TypeScript delivery accounting and tests; it does not change dependencies, workflows, secrets handling, install scripts, or package execution surfaces.

Review details

Best possible solution:

Land this narrow accounting fix after maintainers accept the empty-identity receipt contract, while keeping #87561 open for the broader cross-channel durable delivery contract.

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

Yes, at source level. Current main and v2026.6.9 can still turn identity-less outbound adapter results into non-empty delivered results; I did not run live WeChat or Feishu credentials in this read-only review.

Is this the best way to solve the issue?

Yes, with maintainer acceptance. The shared outbound delivery layer is the narrow maintainable boundary because cron consumes durable-send results; channel-specific workarounds would leave other direct delivery paths inconsistent.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 63874fa0d119.

Label changes

Label justifications:

  • P1: The PR addresses false delivered status for cron/channel delivery, which can hide real message loss in scheduled workflows.
  • merge-risk: 🚨 compatibility: Existing adapters or plugins that returned empty receipts may now produce suppressed or not-delivered outcomes after upgrade.
  • merge-risk: 🚨 message-delivery: The diff controls whether durable sends, hooks, diagnostics, and cron announce paths report sent, suppressed, or delivered across channels.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal output from a local OpenClaw runtime smoke showing an empty Telegram-shaped adapter receipt becomes a suppressed durable send with empty results.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output from a local OpenClaw runtime smoke showing an empty Telegram-shaped adapter receipt becomes a suppressed durable send with empty results.
Evidence reviewed

PR surface:

Source +20, Tests +97. Total +117 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 1 33 13 +20
Tests 2 98 1 +97
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 131 14 +117

What I checked:

  • Repository policy read: Read the full root AGENTS.md and the outbound scoped AGENTS.md; shared channel delivery compatibility and proof guidance apply to this review. (AGENTS.md:21, 63874fa0d119)
  • Live PR state: Live GitHub shows this PR is open, non-draft, has head e046c28, and closes the open cron false-delivered report. (e046c2868b6a)
  • Current main text path still appends identity-less sends: Current main pushes every sendText result into the shared results array; an adapter returning an empty messageId can still make the result set non-empty. (src/infra/outbound/deliver.ts:1514, 63874fa0d119)
  • Current main formatted and media paths have the same accounting shape: Current main pushes formatted-text and media results directly, then classifies non-empty result slices as sent and emits success based on those slices. (src/infra/outbound/deliver.ts:1767, 63874fa0d119)
  • Durable send and cron map non-empty results to delivered: sendDurableMessageBatch returns sent for any non-empty results array, and isolated cron sets delivered from deliveryResults.length > 0 when there is no partial failure. (src/channels/message/send.ts:249, 63874fa0d119)
  • Latest release still has the source-backed gap: v2026.6.9 contains the same direct result-push behavior in text, formatted-text, and media delivery paths, so the fix is not shipped in the latest release. (src/infra/outbound/deliver.ts:1514, c645ec4555c0)

Likely related people:

  • steipete: GitHub history shows foundational durable message lifecycle, message delivery API, and outbound delivery documentation work on the central files involved in this PR. (role: shared outbound delivery foundation contributor; confidence: high; commits: 2ead1502c9bf, a4b17d65a8ff, 5a6eddf5d0d5; files: src/infra/outbound/deliver.ts, src/channels/message/send.ts, src/infra/outbound/deliver-types.ts)
  • piersonr: Recent plugin-SDK reply payload hook work changed the same outbound result, hook, and delivery boundary reviewed here. (role: recent shared outbound hook contributor; confidence: medium; commits: b474f429ee4b; files: src/infra/outbound/deliver.ts, src/channels/message/send.ts, src/infra/outbound/deliver-types.ts)
  • vincentkoc: Recent channel type and diagnostics commits touch nearby durable-send and outbound delivery surfaces that share this result contract. (role: recent channel type contributor; confidence: medium; commits: 5370e73ee984, ef8619d5f539; files: src/channels/message/send.ts, src/infra/outbound/deliver.ts)
  • osolmaz: Authored the open broader durable final fallback issue that explicitly names empty platform identities as part of the cross-channel contract this PR narrows. (role: broader delivery-contract author; confidence: medium; files: src/infra/outbound/deliver.ts, src/channels/message/send.ts, src/channels/plugins/outbound.types.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.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 1, 2026
@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. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 1, 2026
@indulgeback
indulgeback force-pushed the codex/cron-announce-not-delivered-regression branch from 423e5ee to e046c28 Compare June 1, 2026 07:07
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 1, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 1, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jun 2, 2026
@velvet-shark velvet-shark self-assigned this Jun 25, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 26, 2026
@velvet-shark
velvet-shark force-pushed the codex/cron-announce-not-delivered-regression branch from e046c28 to daa223f Compare June 26, 2026 07:40
@velvet-shark
velvet-shark merged commit 9a735be into openclaw:main Jun 26, 2026
90 checks passed
@velvet-shark

Copy link
Copy Markdown
Member

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
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

merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S stale Marked as stale due to inactivity 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.

[Bug]: Cron announce fallback delivery reports success but message never arrives (WeChat + Feishu) on 2026.5.7

2 participants