Hmbown/CodeWhale · error
API timeout should publish an Interrupted mailbox lifecycle…
Error message
API timeout should publish an Interrupted mailbox lifecycle event
What it means
Panic from `.expect("API timeout should publish an Interrupted mailbox lifecycle event")`: the test's polling future completed without ever draining a MailboxMessage::Interrupted from mailbox_rx within 5 seconds. It asserts that an API timeout is surfaced to the parent as an Interrupted lifecycle event with an 'API call timed out' reason.
Solutions
- Check that the API-timeout branch in the sub-agent turn loop publishes MailboxMessage::Interrupted
- Confirm the reason string still contains "API call timed out"
- Verify mailbox_rx is subscribed/connected before the task starts so early events aren't lost
- Increase polling frequency or drain-before-timeout if events arrive after the 5s window
Example fix
// before
.await.expect("API timeout should publish an Interrupted mailbox lifecycle event");
// after
.await.unwrap_or_else(|| panic!("no Interrupted event within 5s; drained={:?}", seen_ids)); Defensive patterns
Strategy: try-catch
Type guard
fn find_interrupted(msgs: &[MailboxMessage]) -> Option<&MailboxMessage> {
msgs.iter().find(|m| matches!(m, MailboxMessage::Interrupted { .. }))
} Try / catch
match tokio::time::timeout(Duration::from_secs(5), poll_interrupted()).await {
Ok(env) => env,
Err(_) => panic!("Interrupted mailbox event never published"),
} Prevention
- Publish lifecycle events before returning from the task so ordering is deterministic
- Use a bounded wait with an assertion on drained messages for diagnostics
- Keep lifecycle reason strings in constants shared with tests
When it happens
Trigger: The sub-agent times out on its API call but the Interrupted mailbox message is never published (or is published with a different variant/topic), or the drain loop misses it before the timeout elapses.
Common situations: Refactor of the mailbox event enum or publish path dropping the Interrupted emission on the timeout branch; reason string changed so the later contains("API call timed out") also fails; channel closed early.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- first timed-out API attempt should reach the test server
- sub-agent task must not park waiting for checkpoint input
- sub-agent task should finish
- Agent not found
- Agent not found
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/8763007b2328e61c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/subagent/tests.rs:9005
.await
.expect("first timed-out API attempt should reach the test server");
let interrupted_envelope = tokio::time::timeout(Duration::from_secs(5), async {
loop {
for env in mailbox_rx.drain() {
if let MailboxMessage::Interrupted {
agent_id: id,
reason,
} = env.message
{
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"
);
View on GitHub (pinned to 433685b202)