Hmbown/CodeWhale · error · anyhow::Error
Cannot follow up agent {agent_id}: status is {} and the chil
Error message
Cannot follow up agent {agent_id}: status is {} and the child cannot resume What it means
followup_child refuses to queue parent mail for a child whose status is Completed, Failed, Cancelled, or BudgetExhausted. Those statuses mean the child loop has exited and can never consume the queued message, so the API fails fast instead of silently dropping mail. Live statuses (Running, Interrupted, needs-input states) are still accepted.
Source
Thrown at crates/tui/src/tools/subagent/mod.rs:5385
self.queue_running_parent_message(&agent_id, text)
}
/// Queue mail and attempt a live wake (`agents/followup`).
pub fn followup_child(&mut self, agent_ref: &str, text: String) -> Result<ParentMailReceipt> {
let agent_id = self.resolve_agent_ref(agent_ref)?;
let status = self
.agents
.get(&agent_id)
.map(|agent| agent.status.clone())
.ok_or_else(|| anyhow!("Agent {agent_id} not found"))?;
if matches!(
status,
SubAgentStatus::Completed
| SubAgentStatus::Failed(_)
| SubAgentStatus::Cancelled
| SubAgentStatus::BudgetExhausted
) {
return Err(anyhow!(
"Cannot follow up agent {agent_id}: status is {} and the child cannot resume",
subagent_status_name(&status)
));
}
let mut receipt = self.queue_parent_message(&agent_id, text.clone(), true)?;
let has_input_tx = self
.agents
.get(&agent_id)
.is_some_and(|agent| agent.input_tx.is_some());
let continuation_handle = self.agents.get(&agent_id).and_then(|agent| {
agent.checkpoint.as_ref().and_then(|cp| {
(cp.continuable && !cp.messages.is_empty()).then(|| cp.continuation_handle.clone())
})
});
let continuable = continuation_handle.is_some();
match status {
SubAgentStatus::Running if has_input_tx => {View on GitHub (pinned to 0c42157ee5)
Solutions
- If the agent is Completed with a continuable checkpoint, use the continue path (continue_child_from_user / resume) which resumes from checkpoint instead of mailing a dead loop
- If Failed or Cancelled, spawn a replacement agent whose prompt includes the prior attempt's result
- Check status first via get_result_by_ref and branch: only Running/Interrupted children accept follow-up
- For BudgetExhausted agents, spawn a new agent with a fresh budget rather than following up
Example fix
// before
let receipt = manager.followup_child(&agent_ref, text)?;
// after
let snap = manager.get_result_by_ref(&agent_ref)?;
match snap.status {
SubAgentStatus::Running | SubAgentStatus::Interrupted(_) => {
let receipt = manager.followup_child(&agent_ref, text)?;
}
SubAgentStatus::Completed => {
// followup refuses Completed; resume from checkpoint instead
manager.continue_child_from_user(shared_handle, runtime, &agent_ref, &text)?;
}
_ => { /* spawn a replacement with prior context */ }
} Defensive patterns
Strategy: type-guard
Validate before calling
// Only mail children whose loop is still alive.
let snap = manager.get_result_by_ref(&agent_ref)?;
anyhow::ensure!(followup_accepts(&snap.status),
"agent {agent_ref} is {} and cannot accept followup", snap.status); Type guard
fn followup_accepts(status: &SubAgentStatus) -> bool {
!matches!(status,
SubAgentStatus::Completed
| SubAgentStatus::Failed(_)
| SubAgentStatus::Cancelled
| SubAgentStatus::BudgetExhausted)
} Try / catch
On 'Cannot follow up agent', inspect the embedded status word: Completed -> switch to the checkpoint-continue path; Failed/Cancelled/BudgetExhausted -> respawn with prior context. Never blindly retry the followup.
Prevention
- Branch on status before sending follow-up mail; make the completed->continue and failed->respawn transitions explicit in orchestration code
- Handle completion events as the trigger to stop mailing a child
- Log the child's status alongside every followup attempt so refusals are diagnosable
When it happens
Trigger: Calling followup_child on an agent that already returned a result; racing a child that completes between the parent's status check and the followup call; following up an agent cancelled by admission pressure or a heartbeat-timeout reaper; following up a BudgetExhausted agent after its token budget ran out.
Common situations: Parent model tries to send 'one more instruction' after reading the child's final output; retry loops that resend mail to a child that failed earlier; fan-out code that messages workers after aggregation, when some already finished.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot continue agent {agent_id}: status is {} and the child
- fleet run {} is already terminal ({lifecycle:?})
- Cannot resume agent {agent_id}: status is {} ({})
- terminal lane transition requires a terminal status
- lane `{}` was stopped before tmux launch
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/53ca85a9135fcd6f.
Report an issue: GitHub.