Skip to content

subagent-announce-delivery: pending direct-announce drops completion notifications across all spawn depths (likely root of #92405) #93323

Description

@oiGaDio

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:

  1. A parent session is processing (model_call active or queueDepth > 0)
  2. AND the gateway event loop is under non-trivial pressure (eventLoopDelayP99Ms > 1000ms was observed)
  3. 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:

  1. 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).
  2. 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.
  3. 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:

  1. 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.
  2. Queue announces when handoff is pending, drain them once the handoff resolves. Avoids drop-on-busy.
  3. 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.
  4. 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

Metadata

Metadata

Assignees

Labels

P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

Type

No type

Fields

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