Summary
The subagent-announce-delivery path fails to deliver subagent announce/completion messages whenever the gateway's internal "direct announce" request returns a pending status. Failed delivery is retried at most 3 times within seconds. After exhausting retries the announce is silently abandoned — the parent session never subscribes to the child's completion events and effectively waits forever.
Today (2026-06-15) on v2026.6.5 this caused at least 11 spawn-child relationships across 3 different parent sessions to lose their announce, producing the user-visible symptom: "subagent completion messages are being lost". This affects depth-1 (orchestrator → task-manager) AND depth-2 (task-manager → coder/checker) spawns equally.
This expands the previously-thought-depth-2-only bug #92405 — same root cause, but exposed on depth-1 too, and the actual mechanism is the completion-handoff state, not the spawn depth.
Affected version
openclaw v2026.6.5 (gateway pid 1242634, running)
- Host: Linux 6.8.0-110-generic aarch64
Reproduction
This reproduces reliably whenever:
- A parent session is processing (model_call active or queueDepth > 0)
- AND the gateway event loop is under non-trivial pressure (
eventLoopDelayP99Ms > 1000ms was observed)
- AND a child subagent run wants to announce itself or its completion
The direct-announce gateway call returns a pending response, which the code interprets as failure for expectsCompletionMessage callers.
Evidence — actual log lines from /tmp/openclaw/openclaw-2026-06-15.log
A. Direct announce returns pending → treated as failure
[warn] Subagent completion direct announce failed for run 55433d2e-...:
gateway request timeout for agent
[warn] Subagent completion direct announce failed for run 55433d2e-...:
completion agent handoff is still pending
[warn] Subagent completion direct announce failed for run 55433d2e-...:
completion agent handoff is still pending
[warn] Subagent announce give up (retry-limit) run=55433d2e-...
child=agent:planner:subagent:b86c0cf6-...
requester=agent:orchestrator:dashboard:c24dd61d-...
retries=3 endedAgo=129s
deliveryError="completion agent handoff is still pending;
direct-primary: completion agent handoff is still pending"
Note that:
- First attempt fails with
gateway request timeout for agent
- Subsequent retries return
completion agent handoff is still pending
- All 3 retries happen within ~10 seconds — the underlying state never gets a chance to recover
B. Today's failure counts (single day on one host)
| Event |
Count |
Subagent completion direct announce failed |
33 |
Subagent announce give up (retry-limit) |
11 |
Subagent announce give up (expiry) |
1 |
subagent suspended delivery discarded |
2 |
| Stall diagnostics emitted |
84 |
liveness warning (event-loop pressure) |
15 |
C. Affected sessions (representative, not exhaustive)
| Parent |
→ Child |
Failure |
| orchestrator c24dd61d |
→ planner b86c0cf6 (depth-1) |
retry-limit |
| TM 349acc23 |
→ checker e2e7ba8c (depth-2) |
retry-limit |
| TM 349acc23 |
→ coder bfc3a170 (depth-2) |
retry-limit |
| TM 349acc23 |
→ coder 55d54ac5 (depth-2) |
retry-limit |
| TM 349acc23 |
→ checker d0d832b9 (depth-2) |
retry-limit |
| TM 349acc23 |
→ coder b8cc0e87 (depth-2) |
retry-limit |
| orchestrator c24dd61d |
→ task-manager a8bc5d80 (depth-1) |
retry-limit |
| orchestrator c24dd61d |
→ task-manager 55e1774f (depth-1) |
expiry (after 1803s) |
Root cause — code location
src/agents/subagent-announce-delivery.ts:1471-1488
const directAnnounceStillPending = isGatewayAgentRunPending(directAnnounceResponse);
if (directAnnounceStillPending) {
if (
params.expectsCompletionMessage &&
expectedMediaUrls.length === 0 &&
!requiresMessageToolDelivery
) {
return {
delivered: false,
path: "direct",
reason: "completion_handoff_pending",
error: "completion agent handoff is still pending",
};
}
return {
delivered: true,
path: "direct",
};
}
The issue:
isGatewayAgentRunPending returns true → the gateway accepted the announce but has not yet completed processing it (likely because event loop is blocked or the run dispatch queue is busy).
- For the
expectsCompletionMessage branch (the standard task-manager spawning a coder/checker pattern), pending is mapped to delivered: false, which the caller interprets as failure.
- For every other code path with the same
pending response, the function returns delivered: true — same status, opposite handling.
This inconsistency means: depending on whether the parent expects a completion message, a pending gateway response either succeeds (silently) or fails (forcing retries that all hit the same blocked state).
Contributing factor — event loop saturation
The gateway is under heavy event-loop pressure when this fires. Liveness warnings around the same window:
liveness warning: reasons=event_loop_delay,cpu interval=30s
eventLoopDelayP99Ms=1199.6 eventLoopDelayMaxMs=1360 eventLoopUtilization=0.821
While a 1.2 s event-loop delay is not great, it is well within "agent runs heavy tools" territory. The bug is the announce-delivery code's reaction to it, not the delay itself.
Suggested fixes
Any one of:
- Treat pending as "wait, do not give up" in the
expectsCompletionMessage branch. The current 3-retry-within-10s pattern guarantees failure under any load — replace with exponential backoff up to, say, 60 s, OR await an explicit completion event from the gateway.
- Queue announces when handoff is pending, drain them once the handoff resolves. Avoids drop-on-busy.
- Make the pending → delivered=true / delivered=false split consistent. The current split (line 1474-1488) is hard to justify: same upstream state, opposite delivery semantics.
- Fail loud: when retries exhaust, emit
recovery=auto_respawn on the diagnostic so the orchestrator/TM can recover, rather than recovery=none which is the current behaviour. Today the gateway detects the stall (stalled session diagnostic fires every 30 s with recovery=none) but takes no recovery action.
Recent commit context
subagent-announce-delivery.ts was last touched in 0f1f1a1f refactor: share startup config recovery test helpers — refactor only, behaviour unchanged. The actual delivery logic is older. Recent work in src/agents/ includes a number of pipeline gating changes (fix(agents): gate finalize hooks before delivery) that may be worth looking at for related state-machine assumptions.
Related
Summary
The
subagent-announce-deliverypath fails to deliver subagent announce/completion messages whenever the gateway's internal "direct announce" request returns apendingstatus. Failed delivery is retried at most 3 times within seconds. After exhausting retries the announce is silently abandoned — the parent session never subscribes to the child's completion events and effectively waits forever.Today (2026-06-15) on v2026.6.5 this caused at least 11 spawn-child relationships across 3 different parent sessions to lose their announce, producing the user-visible symptom: "subagent completion messages are being lost". This affects depth-1 (orchestrator → task-manager) AND depth-2 (task-manager → coder/checker) spawns equally.
This expands the previously-thought-depth-2-only bug #92405 — same root cause, but exposed on depth-1 too, and the actual mechanism is the completion-handoff state, not the spawn depth.
Affected version
openclaw v2026.6.5(gateway pid 1242634, running)Reproduction
This reproduces reliably whenever:
eventLoopDelayP99Ms > 1000mswas observed)The direct-announce gateway call returns a pending response, which the code interprets as failure for
expectsCompletionMessagecallers.Evidence — actual log lines from
/tmp/openclaw/openclaw-2026-06-15.logA. Direct announce returns pending → treated as failure
Note that:
gateway request timeout for agentcompletion agent handoff is still pendingB. Today's failure counts (single day on one host)
Subagent completion direct announce failedSubagent announce give up (retry-limit)Subagent announce give up (expiry)subagent suspended delivery discardedliveness warning(event-loop pressure)C. Affected sessions (representative, not exhaustive)
Root cause — code location
src/agents/subagent-announce-delivery.ts:1471-1488The issue:
isGatewayAgentRunPendingreturns true → the gateway accepted the announce but has not yet completed processing it (likely because event loop is blocked or the run dispatch queue is busy).expectsCompletionMessagebranch (the standard task-manager spawning a coder/checker pattern),pendingis mapped to delivered: false, which the caller interprets as failure.pendingresponse, the function returns delivered: true — same status, opposite handling.This inconsistency means: depending on whether the parent expects a completion message, a
pendinggateway response either succeeds (silently) or fails (forcing retries that all hit the same blocked state).Contributing factor — event loop saturation
The gateway is under heavy event-loop pressure when this fires. Liveness warnings around the same window:
While a 1.2 s event-loop delay is not great, it is well within "agent runs heavy tools" territory. The bug is the announce-delivery code's reaction to it, not the delay itself.
Suggested fixes
Any one of:
expectsCompletionMessagebranch. The current 3-retry-within-10s pattern guarantees failure under any load — replace with exponential backoff up to, say, 60 s, OR await an explicit completion event from the gateway.recovery=auto_respawnon the diagnostic so the orchestrator/TM can recover, rather thanrecovery=nonewhich is the current behaviour. Today the gateway detects the stall (stalled session diagnosticfires every 30 s withrecovery=none) but takes no recovery action.Recent commit context
subagent-announce-delivery.tswas last touched in0f1f1a1f refactor: share startup config recovery test helpers— refactor only, behaviour unchanged. The actual delivery logic is older. Recent work insrc/agents/includes a number of pipeline gating changes (fix(agents): gate finalize hooks before delivery) that may be worth looking at for related state-machine assumptions.Related