openclaw - 💡(How to fix) Fix [Bug]: deliverySucceeded=true returned when no adapter was invoked (early returns in deliverOutboundPayloads masquerade as success) [2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#78532Fetched 2026-05-07 03:35:48
View on GitHub
Comments
2
Participants
3
Timeline
9
Reactions
2
Timeline (top)
mentioned ×3subscribed ×3commented ×2closed ×1

deliverySucceeded flips to true whenever no per-payload onError fires, including code paths that exit deliverOutboundPayloads without ever invoking a channel adapter. Result: the gateway reports successful delivery for replies that were never sent. There is no gateway.log line indicating suppression or the silent no-op, so installs go dark for days without any signal.

Error Message

  1. message_tool plugin not in plugins.allow — same shape: adapter not invoked, no error, success reported. Default deliverySucceeded to false and only flip it true when an adapter explicitly succeeds (positive acknowledgment, not absence of error). Concretely:
  • Add a gateway.log line at WARN whenever delivery exits without adapter invocation, including the reason (claim-conflict, bindings-empty, tool-unavailable, etc.). Silent no-ops are the diagnostic killer here.

Root Cause

This is the difference between "agent looks broken for an hour" and "agent looks broken for 7 days." With deliverySucceeded=true lying, every operator-facing health check, log dashboard, and probe says delivery is fine, so nobody investigates. The actual missing message is only discoverable by querying the channel API directly (GET /channels/<id>/messages) and noticing the bot's reply isn't there.

This is structurally distinct from the message_tool_only regression in #77260:

  • #77260: delivery policy suppressed certain replies. Fix is the source-policy bypass merged in a2f1d1dfd8ab.
  • This issue: delivery telemetry returns success for paths where no adapter was invoked at all. Independent of policy, independent of channel.

Fix Action

Fix / Workaround

This is structurally distinct from the message_tool_only regression in #77260:

  • #77260: delivery policy suppressed certain replies. Fix is the source-policy bypass merged in a2f1d1dfd8ab.
  • This issue: delivery telemetry returns success for paths where no adapter was invoked at all. Independent of policy, independent of channel.
RAW_BUFFERClick to expand / collapse

Filed as a follow-up to #77260 per @martingarramon's suggestion to split the delivery telemetry bug from the visibleReplies regression. #77260 was closed as the command-reply scope is fixed on main; this telemetry issue is still live.

Summary

deliverySucceeded flips to true whenever no per-payload onError fires, including code paths that exit deliverOutboundPayloads without ever invoking a channel adapter. Result: the gateway reports successful delivery for replies that were never sent. There is no gateway.log line indicating suppression or the silent no-op, so installs go dark for days without any signal.

Code references (current main)

  • src/agents/command/delivery.ts:357let deliverySucceeded = false; let deliveryHadError = false;
  • src/agents/command/delivery.ts:374deliveryHadError = true; (only set inside per-payload onError: markDeliveryError)
  • src/agents/command/delivery.ts:398deliverySucceeded = !deliveryHadError;
  • src/infra/outbound/deliver.ts:1084export async function deliverOutboundPayloads(...)
  • src/infra/outbound/deliver.ts:1140if (claimResult.status === "claimed-by-other-owner") { return []; } (early return, no onError invoked)

The !deliveryHadError semantics mean any return path that doesn't iterate payloads — claim conflict, bindings-empty Discord threads, message_tool plugin not in plugins.allow, etc. — returns to the caller with deliveryHadError=false, and line 398 reports success.

Reproduced silent-success paths

  1. Claim conflictclaimed-by-other-owner at deliver.ts:1140 returns [] directly. Caller flips deliverySucceeded=true.
  2. Discord thread bindings empty — when ~/.openclaw/discord/thread-bindings.json is missing or its entry was evicted by a session-store cap fire, resolveBoundThreadBinding(sessionKey) returns empty, the Discord adapter is never invoked, and the call returns without any onError firing. CLI --reply-to probe with --deliver returned deliverySucceeded: true while Discord API confirmed the bot's reply never posted (last 15 messages 100% inbound over 7 days).
  3. message_tool plugin not in plugins.allow — same shape: adapter not invoked, no error, success reported.

In each case the trajectory shows session.started → ... → model.completed → trace.artifacts → session.ended with no delivery event between model.completed and session.ended, the session JSONL records the reply text, and deliverySucceeded returns true.

Why this matters

This is the difference between "agent looks broken for an hour" and "agent looks broken for 7 days." With deliverySucceeded=true lying, every operator-facing health check, log dashboard, and probe says delivery is fine, so nobody investigates. The actual missing message is only discoverable by querying the channel API directly (GET /channels/<id>/messages) and noticing the bot's reply isn't there.

This is structurally distinct from the message_tool_only regression in #77260:

  • #77260: delivery policy suppressed certain replies. Fix is the source-policy bypass merged in a2f1d1dfd8ab.
  • This issue: delivery telemetry returns success for paths where no adapter was invoked at all. Independent of policy, independent of channel.

Suggested fix shape

Default deliverySucceeded to false and only flip it true when an adapter explicitly succeeds (positive acknowledgment, not absence of error). Concretely:

  • Track deliveryAdapterSucceeded: boolean set by adapters on confirmed send.
  • Replace deliverySucceeded = !deliveryHadError with deliverySucceeded = deliveryAdapterSucceeded.
  • Audit every early return in deliverOutboundPayloads and downstream paths to confirm none of them reach the success flip without invoking an adapter.
  • Add a gateway.log line at WARN whenever delivery exits without adapter invocation, including the reason (claim-conflict, bindings-empty, tool-unavailable, etc.). Silent no-ops are the diagnostic killer here.

Suggested test additions

  • src/agents/command/delivery.test.ts — assert deliverySucceeded === false on the claimed-by-other-owner path.
  • src/infra/outbound/deliver.test.ts — assert deliverySucceeded === false when deliverOutboundPayloads exits without iterating payloads.
  • A Discord-thread variant asserting deliverySucceeded === false when resolveBoundThreadBinding returns empty.
  • A message_tool-not-in-plugins.allow variant asserting the same.

Environment of the original repro (from #77260)

  • OpenClaw 2026.5.4 (built from main, channel=dev)
  • macOS 14.x, npm install
  • Discord public thread (type=11) under guild text channel parent
  • messages.groupChat.visibleReplies = "message_tool" (default after doctor --fix)
  • message_tool plugin NOT in plugins.allow

Trajectory JSONL, session JSONL, and --reply-to probe transcript available on request for regression-test seed data.

cc @martingarramon @vincentkoc — flagging per Martin's split suggestion in https://github.com/openclaw/openclaw/issues/77260#issuecomment-4387695404

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [Bug]: deliverySucceeded=true returned when no adapter was invoked (early returns in deliverOutboundPayloads masquerade as success) [2 comments, 3 participants]