Skip to content

Bug: Subagent completion announcements silently drop on Telegram when parent session is idle between turns #75663

@yozakura-ava

Description

@yozakura-ava

Summary

When a subagent spawned via sessions_spawn completes its work in the background, the completion announcement (result) is silently dropped and never delivered to the Telegram user. The user only sees the result after sending another message that re-triggers the session.

Environment

  • OpenClaw version: 2026.4.29 (installed via npm)
  • Channel: Telegram (push-based delivery)
  • Node: v22.22.2
  • OS: Linux 6.8.0-107-generic (x64)
  • Less affected: WebChat (polling picks up queued messages eventually)

Steps to Reproduce

  1. Send a message on Telegram that triggers a sessions_spawn call
  2. Wait for the subagent to complete (can verify via subagents list or watching logs)
  3. Observe: no response is delivered to Telegram
  4. Send another arbitrary message → the subagent result appears immediately (it was queued as a steer message)

Root Cause

In src/agents/subagent-announce-delivery.ts, function sendSubagentAnnounceDirectly:

When the parent session has an active embedded Pi run that is not actively consuming messages (between turns, waiting for LLM response, or post-response idle), the following sequence occurs:

  1. queueEmbeddedPiMessage returns false (correctly — the run isn't streaming)
  2. The code enters the requesterActivity.isActive block and tries sendCompletionFallback
  3. sendCompletionFallback often fails (empty completionFallbackText, no direct channel target available)
  4. The function returns { delivered: false } as a dead-end — even though a perfectly good callGateway("agent", ...) path exists ~40 lines below that would start a new run and deliver the message

The early return at the end of the if (requesterActivity.isActive) block prevents the code from falling through to the gateway call that would actually deliver the result.

Key Code Path

// Line ~756-778 in subagent-announce-delivery.ts
if (requesterActivity.isActive) {
  try {
    const didFallback = await sendCompletionFallback({...});
    if (didFallback) {
      return { delivered: true, path: "..." };  // ✓ correct
    }
  } catch (err) {
    return { delivered: false, ... };  // ✗ DEAD END — should fall through
  }
  return { delivered: false, error: "could not be woken" };  // ✗ DEAD END — should fall through
}

// Line ~795+ — the working gateway call that never gets reached
directAnnounceResponse = await runAnnounceDeliveryWithRetry({
  ...callGateway("agent", { message, deliver: true, expectFinal: true })...
});

Proposed Fix

Remove the early return { delivered: false } statements in the steer-fallback failure paths (the catch block and the post-try fallback). Let execution fall through to the existing callGateway("agent", ...) path that already handles creating a new run and delivering the message.

One file, ~10 lines changed. No new dependencies, no architectural changes. The delivery infrastructure already exists — it just isn't reachable from this code path.

Impact

  • User-facing: Subagent results silently disappear on Telegram until next user message
  • Cron announcements: Same pattern — background cron job completions may not reach the user
  • WebChat: Less affected because the UI polls and picks up queued messages
  • Frequency: Intermittent — depends on timing between subagent completion and parent session state

Workaround

Increasing agents.defaults.subagents.announceTimeoutMs from the default 120s to 180s provides marginal improvement by extending the retry window, but does not fix the architectural dead-end.

Labels

bug, channel:telegram, area:subagents, priority:medium

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