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

  1. 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
  2. If Failed or Cancelled, spawn a replacement agent whose prompt includes the prior attempt's result
  3. Check status first via get_result_by_ref and branch: only Running/Interrupted children accept follow-up
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/53ca85a9135fcd6f. Report an issue: GitHub.