Skip to content

Telegram subagent completion fallback can queue raw child/internal output after mediated announce failure #78531

@EthanSK

Description

@EthanSK

Summary

On OpenClaw 2026.5.5 (b1abf9d as reported by openclaw status), a sub-agent completion/announce failure path can queue or attempt to deliver raw child output to Telegram instead of a mediated, user-safe summary.

This appears distinct from the already-fixed updater/runtime-context leak: the remaining path is in sub-agent completion fallback / delivery recovery after the parent-agent mediated completion failed.

Related existing issues: #39032 and #25592.

Environment

  • OpenClaw: 2026.5.5 (b1abf9d)
  • Installed package: openclaw@2026.5.5
  • npm latest at time checked: 2026.5.5
  • OS/runtime: macOS 26.4.1 arm64, Node 25.6.1
  • Gateway: LaunchAgent, /opt/homebrew/lib/node_modules/openclaw/dist/index.js gateway --port 18789
  • Channel: Telegram direct message

Evidence observed locally (redacted)

A failed queued delivery existed at:

/Users/ethansk/.openclaw/delivery-queue/37bd4b88-388d-4270-8c5a-415f3e2e1b87.json

Metadata from that queued item:

  • id: 37bd4b88-388d-4270-8c5a-415f3e2e1b87
  • channel: telegram
  • to: telegram:6164541473
  • accountId: default
  • session key: agent:main:telegram:default:direct:6164541473
  • queued payload text length: 15962
  • retryCount: 3
  • lastError: Message must be non-empty for Telegram sends
  • enqueuedAt: 2026-05-06T11:56:26.534Z
  • lastAttemptAt: 2026-05-06T15:01:51.471Z

The queued payload contained raw/internal indicators including:

  • sawTargetNormalizationGain
  • target-normalization-gain-not-applied
  • missing-in-memory-cache
  • BEGIN_UNTRUSTED_CHILD_RESULT
  • BEGIN_OPENCLAW_INTERNAL_CONTEXT

I am deliberately not pasting the raw payload here because it is exactly the material that should not be exposed to Telegram or GitHub.

Relevant log/run identifiers:

  • Log: /tmp/openclaw/openclaw-2026-05-06.log
  • Run/event id: announce:v1:agent:main:subagent:f5f3eaaf-58a2-4322-aa1b-06e588aed2a7:d1e6ffed-0974-41c8-839f-57b714fb7e88
  • Provider failure seen near this path: service_unavailable_error / server_is_overloaded
  • Raw error hash: sha256:115e5180d97d
  • Raw error fingerprint: sha256:21e6e417518f

Likely failure path

From inspecting the installed dist files, the dangerous path appears to be:

  1. A sub-agent completes and subagent-announce-delivery tries to wake or mediate the requester session.
  2. The mediated parent-agent completion fails or is incomplete, e.g. due to provider overload.
  3. The completion fallback path uses task completion event content directly.
  4. The outbound delivery queue persists payloads before final Telegram/channel sanitization, so raw child/internal content can remain in the retry queue.
  5. Delivery recovery later retries that queued item. In this case it failed with Message must be non-empty for Telegram sends, likely because a later sanitizer/renderer stripped enough content to empty it, but the unsafe payload was still persisted and retried.

Files inspected locally:

  • /opt/homebrew/lib/node_modules/openclaw/dist/subagent-announce-delivery-De-sCDmZ.js
  • /opt/homebrew/lib/node_modules/openclaw/dist/delivery-queue-CX8x6Ho_.js
  • /opt/homebrew/lib/node_modules/openclaw/dist/send-C3qlmCZc.js
  • /opt/homebrew/lib/node_modules/openclaw/dist/sanitize-text-IbfTPlxP.js

Local mitigation I applied

This was a local hotfix in the installed dist only, not an upstream PR.

1. Stop direct fallback from using raw child event.result

Patched:

/opt/homebrew/lib/node_modules/openclaw/dist/subagent-announce-delivery-De-sCDmZ.js

Changed extractTaskCompletionFallbackText(event) so it no longer returns event.result. It now emits only task/status metadata plus a safe notice:

The child result was withheld from direct delivery so it can be summarized safely in-session.

Verification after patch:

  • safe notice present: yes
  • event.result referenced inside extractTaskCompletionFallbackText: no
  • backup created: subagent-announce-delivery-De-sCDmZ.js.bak-unsafe-child-fallback

2. Add outbound delimiter stripping for internal context / untrusted child result blocks

Patched:

/opt/homebrew/lib/node_modules/openclaw/dist/sanitize-text-IbfTPlxP.js

Added stripping for delimited blocks:

  • <<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>> ... <<<END_OPENCLAW_INTERNAL_CONTEXT>>>
  • <<<BEGIN_UNTRUSTED_CHILD_RESULT>>> ... <<<END_UNTRUSTED_CHILD_RESULT>>>

Verification after patch:

  • BEGIN_OPENCLAW_INTERNAL_CONTEXT guard present: yes
  • BEGIN_UNTRUSTED_CHILD_RESULT guard present: yes
  • backup created: sanitize-text-IbfTPlxP.js.bak-runtime-delimiters

3. Quarantine the unsafe queued item

Moved the unsafe pending queue entry out of active retry path:

/Users/ethansk/.openclaw/delivery-queue/failed/37bd4b88-388d-4270-8c5a-415f3e2e1b87.quarantined-unsafe-child-result-20260506T151901Z.json

Verification after quarantine:

  • pending /Users/ethansk/.openclaw/delivery-queue/*.json: empty
  • matching quarantined file exists under delivery-queue/failed/

Expected upstream fix

OpenClaw should make this impossible in more than one layer:

  1. Sub-agent completion fallback must never use raw child event.result for external channel delivery.
  2. Outbound queue entries should store already-sanitized external-delivery payloads, or reject/quarantine entries that contain internal runtime context / untrusted child result delimiters.
  3. Delivery recovery should fail closed for unsafe payloads instead of retrying them to Telegram/other channels.
  4. Tests should cover provider-failure/incomplete-mediated-completion paths, not just successful mediated summaries.
  5. Telegram/direct channel rendering should never receive BEGIN_OPENCLAW_INTERNAL_CONTEXT or BEGIN_UNTRUSTED_CHILD_RESULT blocks as sendable text.

Open question

The local queue item may or may not have been fully delivered before being sanitized/retried; logs confirmed retry failures for this queue id, but the original Telegram-visible message report came from a user-observed message (8995). Either way, raw/internal content reached a send/retry path and should be treated as a privacy/safety bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions