Skip to content

Sub-agent run timeout does not notify parent session (completion event silently dropped) #89095

Description

@sunnydongbo

Summary

When a sub-agent run is force-killed by its run timeout (runTimeoutSeconds / subagents.runTimeoutSeconds), the parent session receives no completion/timeout event. The sub-agent registry correctly records status: "timeout" with an endedAt, but the parent agent is never notified — it waits indefinitely and only discovers the death by actively polling subagents(action=list).

Normal completions (and graceful exits that still produce output) notify the parent reliably. The gap is specific to the timeout/force-kill termination path.

Version: 2026.5.28 (e932160).

Reproduction

  1. From a parent agent, spawn a sub-agent with a short run timeout and a blocking task, e.g. runTimeoutSeconds: 30 and a task that runs sleep 600.
  2. The runtime force-kills the child at 30s; subagents(action=list) shows status: "timeout", endedAt set.
  3. The parent session receives no completion event — no auto-announce message arrives. Contrast with a normally-finishing sub-agent, whose completion arrives as a parent message within seconds.

Root cause

In the wait-timer fallback inside waitForAgentJob (dist/agent-D5XvMyya.js, ~line 339), the timeout callback only consults getPendingAgentRunError and ignores getPendingAgentRunTimeout:

const timer = setSafeTimeout(() => {
    const pendingError = getPendingAgentRunError(runId);
    finish(pendingError ? createPendingErrorTimeoutSnapshot(pendingError.snapshot) : null);
}, timeoutMs);

When the run timeout fires, the gateway force-kills the child and a pending timeout snapshot (carrying endedAt) is registered. But the wait timer fires near-simultaneously and calls finish(null) because there is no pending error — discarding the timeout snapshot and cancelling the grace timer. The resolved wait result then lacks endedAt/stopReason, so isTerminalWaitTimeout is false downstream and the announce/notify path never delivers a parent event.

The normal scheduled timeout path already resolves pendingTimeout.snapshot directly, so the wait-timer fallback is simply asymmetric — it handles pending errors but not pending timeouts.

Suggested fix

Make the wait-timer fallback symmetric — fall back to the pending timeout snapshot when there is no pending error:

const timer = setSafeTimeout(() => {
    const pendingError = getPendingAgentRunError(runId);
    if (pendingError) {
        finish(createPendingErrorTimeoutSnapshot(pendingError.snapshot));
        return;
    }
    const pendingTimeout = getPendingAgentRunTimeout(runId);
    finish(pendingTimeout ? pendingTimeout.snapshot : null);
}, timeoutMs);

getPendingAgentRunTimeout(runId) already exists with a signature matching getPendingAgentRunError (both return { snapshot, dueAt } or undefined). This preserves error precedence and keeps the timeout snapshot's endedAt, so isTerminalWaitTimeout becomes true and the parent receives the timeout completion.

Possibly related (not fixed here)

There is a secondary "no-output" case in the announce delivery layer (subagent-announce-delivery-*.js, hasFailedSubagentNoOutputCompletion): a timed-out sub-agent that produced literally (no output) is marked delivered:false and dropped after retry. This is plausibly intentional (don't push an empty message), but worth confirming it doesn't compound the silent-timeout symptom for the parent-session event specifically.

This was found via adversarial testing of the sub-agent comm path; the wait-timer fix above resolved the core "silent timeout" symptom locally. Filing for an upstream fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.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.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions