-
-
Notifications
You must be signed in to change notification settings - Fork 80.7k
Final reply is never delivered: dispatcher.waitForIdle() blocks forever on a beforeDeliver hook, and the lane stays blocked #103684
Copy link
Copy link
Closed
Labels
P0Emergency: data loss, security bypass, crash loop, or unusable core runtime.Emergency: data loss, security bypass, crash loop, or unusable core runtime.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.ClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.ClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.Channel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.Session, memory, transcript, context, or agent state can drift or corrupt.impact:ux-release-blockerA non-technical user is blocked without terminal, logs, config, or support.A non-technical user is blocked without terminal, logs, config, or support.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.Issue affects a taxonomy feature currently scored M4/M5.no-staleExclude from stale automationExclude from stale automation
Description
Metadata
Metadata
Assignees
Labels
P0Emergency: data loss, security bypass, crash loop, or unusable core runtime.Emergency: data loss, security bypass, crash loop, or unusable core runtime.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.ClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.ClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.Channel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.Session, memory, transcript, context, or agent state can drift or corrupt.impact:ux-release-blockerA non-technical user is blocked without terminal, logs, config, or support.A non-technical user is blocked without terminal, logs, config, or support.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.Issue affects a taxonomy feature currently scored M4/M5.no-staleExclude from stale automationExclude from stale automation
Type
Fields
Priority
None yet
Version: openclaw
2026.6.11(npmlatest)Runtime: node v24.18.0, Linux 6.12.75 (arm64, Raspberry Pi)
Channel: whatsapp (plugin,
@openclaw/whatsapp2026.6.11)Agent backend:
claude-cli, single agent, single channelSummary
An agent run completes normally (
claude live session turn: … outBytes=2288), the replypayload is queued on the reply dispatcher, and then nothing is ever sent. No error, no
timeout, no abort. The reply is persisted as
pendingFinalDeliveryinsessions.json.Because
pendingFinalDeliveryis only cleared after a successful delivery, and because alane with a pending final delivery does not start new runs, the lane is permanently
blocked: every subsequent inbound message only gets ack reactions (
✅ 💭 ⏳ ⚠️) and norun is ever dispatched. Recovery requires stopping the gateway and editing
sessions.jsonby hand.
Where it hangs (instrumented, not inferred)
I added temporary probes to the installed bundles and reproduced this several times. Probe
output, in order, for one hanging delivery:
So:
dispatch-DXwxohlw.js→sendFinalPayload→dispatcher.sendFinalReply(payload)returnstrue, i.e. the payload is enqueued ontosendChainincreateReplyDispatcher(
reply-dispatcher.types-frqf6trh.js).await mirrorTranscriptAfterDispatcherDelivery(...), whose firstline is
await params.dispatcher.waitForIdle(), andwaitForIdle: () => sendChain.await beforeDeliver(normalized, dispatchInfo)neverreturns. Neither
[PROBE] Q3(afterbeforeDeliver) nor any error appears.Result:
sendChainnever settles →waitForIdle()never resolves →sendFinalPayloadnever returns → the dispatch never completes → the enqueued payload is never flushed.
This is a deadlock between the dispatcher's idle wait and its own queued send.
I could not identify which
beforeDeliversegment blocks. Probes insidecombineBeforeDeliverHooks,buildReplyPayloadSendingBeforeDeliver, and the wrapperinstalled by
appendBeforeDeliverwere never entered, so the blocking function is theoptions.beforeDeliverpassed at dispatcher construction from a bundle other thandispatch-DXwxohlw.js. Someone who knows which module builds the channel reply pipeline'sbeforeDelivershould be able to find it immediately.Note that
runReplyPayloadSendingHookhas a timeout (getModifyingHookTimeoutMs), but thebeforeDelivercomposition around it does not, so a hook-adjacent await that never settleshangs the whole chain.
Impact
pendingFinalDeliveryis set (it is written before delivery as a restart-recovery net).reactions and queued (
message queued: … queueDepth=7 sessionState=processing), but nocli execis ever issued.main-session-restart-recoveryonly replayspendingFinalDeliveryfor the main session,so a channel lane never recovers on its own — not even across a gateway restart.
message_ids on every reconnect, which reproduces the hang and looks like a channel loop.Reproduction
claude-clibackend, WhatsApp DM lane.Sending message,pendingFinalDelivery: true.cli exec,queueDepthgrows.Not every run hangs — short runs delivered fine (e.g. a 14 s run at 10:52 delivered in
27 ms). The hang correlates with longer runs and with queued follow-up messages, but I have
not isolated the trigger.
Suggested fixes
waitForIdle()on the same chain you just enqueued to inmirrorTranscriptAfterDispatcherDelivery. Await the specific delivery, or mirror thetranscript from the chain's own completion callback.
beforeDeliver. The reply-payload hook already has a timeout; thecomposed
beforeDelivershould too, and a timeout must fail the link rather than hang it.pendingFinalDeliverynon-blocking. A lane that has an undelivered reply shouldstill accept new runs, or a recovery pass should flush it (today only the main session
gets that). As it stands, a single missed delivery bricks the lane until someone edits
sessions.jsonwith the gateway stopped.Related
Filed alongside #103599 (
reply session initialization conflicted), which drops thesecond inbound message while a run is active. Both together make a DM lane look dead:
one loses the message, the other loses the answer.