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
- Send a message on Telegram that triggers a
sessions_spawn call
- Wait for the subagent to complete (can verify via
subagents list or watching logs)
- Observe: no response is delivered to Telegram
- 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:
queueEmbeddedPiMessage returns false (correctly — the run isn't streaming)
- The code enters the
requesterActivity.isActive block and tries sendCompletionFallback
sendCompletionFallback often fails (empty completionFallbackText, no direct channel target available)
- 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
Summary
When a subagent spawned via
sessions_spawncompletes 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
Steps to Reproduce
sessions_spawncallsubagents listor watching logs)Root Cause
In
src/agents/subagent-announce-delivery.ts, functionsendSubagentAnnounceDirectly: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:
queueEmbeddedPiMessagereturnsfalse(correctly — the run isn't streaming)requesterActivity.isActiveblock and triessendCompletionFallbacksendCompletionFallbackoften fails (emptycompletionFallbackText, no direct channel target available){ delivered: false }as a dead-end — even though a perfectly goodcallGateway("agent", ...)path exists ~40 lines below that would start a new run and deliver the messageThe 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
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 existingcallGateway("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
Workaround
Increasing
agents.defaults.subagents.announceTimeoutMsfrom 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