Hmbown/CodeWhale · error
Cannot follow up agent
Error message
Cannot follow up agent {agent_id}: status is {} and the child cannot resume What it means
Thrown by `followup_child` in the subagent registry when a caller sends a follow-up (`agents/followup`) to a child agent whose status is a terminal state: Completed, Failed, Cancelled, or BudgetExhausted. Terminal records are immutable receipts, not mailboxes, so the message surface fails closed rather than acknowledging undeliverable work. Only Running, Interrupted, or Interrupted-with-checkpoint children can accept follow-ups.
Solutions
- Check the child's status before following up and use the continue/continue_agent path for Completed or Interrupted children instead of followup.
- Start a new child agent and deliver the message as its initial task if the old child is terminal.
- If the child finished, incorporate its recorded output/receipt rather than sending more mail.
- If BudgetExhausted, raise the budget or spawn a fresh agent with a larger allowance.
Example fix
// before
let receipt = registry.followup_child(&agent_ref, text)?;
// after
let status = registry.status_of(&agent_ref);
let receipt = if matches!(status, SubAgentStatus::Running | SubAgentStatus::Interrupted(_)) {
registry.followup_child(&agent_ref, text)?
} else {
registry.continue_agent(&agent_ref, text)? // resume path for terminal/interrupted children
}; Defensive patterns
Strategy: validation
Validate before calling
let status = registry.status_of(&agent_ref);
if matches!(status, SubAgentStatus::Completed | SubAgentStatus::Failed(_) | SubAgentStatus::Cancelled | SubAgentStatus::BudgetExhausted) {
// terminal: use continue_agent or spawn a new child instead
} Type guard
fn is_resumable(status: &SubAgentStatus) -> bool {
!matches!(status, SubAgentStatus::Completed | SubAgentStatus::Failed(_) | SubAgentStatus::Cancelled | SubAgentStatus::BudgetExhausted)
} Try / catch
match registry.followup_child(&agent_ref, text) {
Ok(receipt) => /* delivered or queued */,
Err(e) if e.to_string().contains("cannot resume") => {
let _ = registry.continue_agent(&agent_ref, text); // fall back to resume path
}
Err(e) => return Err(e),
} Prevention
- Always read the child's status before any follow-up or continue call.
- Treat terminal statuses as immutable receipts; never send mail to them.
- Route Completed/Interrupted children through the continue path, terminal-failed ones through a new spawn.
- Handle BudgetExhausted by raising budget + new spawn, not by retrying messages.
When it happens
Trigger: Calling `SubagentRegistry::followup_child(agent_ref, text)` (or the `agents/followup` tool) when the resolved child already finished successfully, failed, was cancelled, or exhausted its budget.
Common situations: A UI or orchestrator races a follow-up against a child that just finished; an agent was cancelled by the user before the follow-up arrived; a retry loop keeps messaging an agent that already failed; a budget-exhausted child is still addressed by stale code paths.
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
- Cannot resume agent : status is ( )
- Agent continuation target is no longer retained
- Agent continuation target is outside the active session
- Agent not found
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/53ca85a9135fcd6f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/subagent/mod.rs:5845
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 73e0f67d83)