Hmbown/CodeWhale · error

sub-agent task should finish

Error message

sub-agent task should finish

What it means

Panic from `.expect("sub-agent task should finish")` on the JoinHandle result: the outer timeout passed but the task itself returned Err (joined error / panicked inside the task). The task completed after the timeout interruption but failed instead of finishing cleanly.

Solutions

  1. Run with RUST_BACKTRACE=1 and read the inner task panic to find the failing point inside the sub-agent task
  2. Check that the retry-exhausted path returns Ok (finished status) rather than erroring
  3. Verify channels the task depends on are not closed/dropped before the final retry completes

Example fix

// before
.await.expect("sub-agent task must not park waiting for checkpoint input")
    .expect("sub-agent task should finish");
// after
.await.unwrap_or_else(|_| panic!("task did not complete in time"))
    .unwrap_or_else(|e| panic!("task finished with error: {e}"));
Defensive patterns

Strategy: try-catch

Try / catch

match tokio::time::timeout(Duration::from_secs(5), task_handle).await {
    Ok(Ok(())) => {},
    Ok(Err(e)) => panic!("task errored: {e}"),
    Err(_) => panic!("task timed out"),
}

Prevention

When it happens

Trigger: task_handle resolves to Err because the spawned sub-agent task panicked or returned an error after exhausting timeout retries, e.g., unwrap on an expected channel/message that was consumed by the mailbox drain.

Common situations: Task panic on a closed channel after retries exhausted; assertion inside the task failing on the final attempt; JoinError from an aborted task.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/5d96c64d2f7a1cdb. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/subagent/tests.rs:9016

                    return (id, reason);
                }
            }
            tokio::time::sleep(Duration::from_millis(10)).await;
        }
    })
    .await
    .expect("API timeout should publish an Interrupted mailbox lifecycle event");
    assert_eq!(interrupted_envelope.0, agent_id);
    assert!(
        interrupted_envelope.1.contains("API call timed out"),
        "reason should carry the timeout context: {}",
        interrupted_envelope.1
    );

    tokio::time::timeout(Duration::from_secs(5), task_handle)
        .await
        .expect("sub-agent task must not park waiting for checkpoint input")
        .expect("sub-agent task should finish");
    assert_eq!(
        calls.load(Ordering::SeqCst),
        SUBAGENT_API_TIMEOUT_MAX_RETRIES.saturating_add(1) as usize,
        "needs-input interruption must not park for continuation; the API call \
         is retried up to the timeout-retry budget, then stops"
    );

    let interrupted = {
        let manager = manager.read().await;
        manager
            .get_result(&agent_id)
            .expect("agent should stay registered")
    };
    assert!(matches!(interrupted.status, SubAgentStatus::Interrupted(_)));
    let checkpoint = interrupted
        .checkpoint
        .as_ref()
        .expect("timeout should preserve checkpoint");

View on GitHub (pinned to 433685b202)