{"record":{"id":"53ca85a9135fcd6f","repo":"Hmbown/CodeWhale","slug":"cannot-follow-up-agent-agent-id-status-is-an","errorCode":null,"errorMessage":"Cannot follow up agent {agent_id}: status is {} and the child cannot resume","messagePattern":"Cannot follow up agent (.+?): status is (.+?) and the child cannot resume","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":5385,"sourceCode":"        self.queue_running_parent_message(&agent_id, text)\n    }\n\n    /// Queue mail and attempt a live wake (`agents/followup`).\n    pub fn followup_child(&mut self, agent_ref: &str, text: String) -> Result<ParentMailReceipt> {\n        let agent_id = self.resolve_agent_ref(agent_ref)?;\n        let status = self\n            .agents\n            .get(&agent_id)\n            .map(|agent| agent.status.clone())\n            .ok_or_else(|| anyhow!(\"Agent {agent_id} not found\"))?;\n        if matches!(\n            status,\n            SubAgentStatus::Completed\n                | SubAgentStatus::Failed(_)\n                | SubAgentStatus::Cancelled\n                | SubAgentStatus::BudgetExhausted\n        ) {\n            return Err(anyhow!(\n                \"Cannot follow up agent {agent_id}: status is {} and the child cannot resume\",\n                subagent_status_name(&status)\n            ));\n        }\n        let mut receipt = self.queue_parent_message(&agent_id, text.clone(), true)?;\n        let has_input_tx = self\n            .agents\n            .get(&agent_id)\n            .is_some_and(|agent| agent.input_tx.is_some());\n        let continuation_handle = self.agents.get(&agent_id).and_then(|agent| {\n            agent.checkpoint.as_ref().and_then(|cp| {\n                (cp.continuable && !cp.messages.is_empty()).then(|| cp.continuation_handle.clone())\n            })\n        });\n        let continuable = continuation_handle.is_some();\n\n        match status {\n            SubAgentStatus::Running if has_input_tx => {","sourceCodeStart":5367,"sourceCodeEnd":5403,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/subagent/mod.rs#L5367-L5403","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet receipt = manager.followup_child(&agent_ref, text)?;\n\n// after\nlet snap = manager.get_result_by_ref(&agent_ref)?;\nmatch snap.status {\n    SubAgentStatus::Running | SubAgentStatus::Interrupted(_) => {\n        let receipt = manager.followup_child(&agent_ref, text)?;\n    }\n    SubAgentStatus::Completed => {\n        // followup refuses Completed; resume from checkpoint instead\n        manager.continue_child_from_user(shared_handle, runtime, &agent_ref, &text)?;\n    }\n    _ => { /* spawn a replacement with prior context */ }\n}","handlingStrategy":"type-guard","validationCode":"// Only mail children whose loop is still alive.\nlet snap = manager.get_result_by_ref(&agent_ref)?;\nanyhow::ensure!(followup_accepts(&snap.status),\n    \"agent {agent_ref} is {} and cannot accept followup\", snap.status);","typeGuard":"fn followup_accepts(status: &SubAgentStatus) -> bool {\n    !matches!(status,\n        SubAgentStatus::Completed\n        | SubAgentStatus::Failed(_)\n        | SubAgentStatus::Cancelled\n        | SubAgentStatus::BudgetExhausted)\n}","tryCatchPattern":"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.","preventionTips":["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"],"tags":["subagent","rust","lifecycle","invalid-state-transition","followup"],"backgroundTag":"invalid-state-transition","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}