{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":5845,"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":5827,"sourceCodeEnd":5863,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/subagent/mod.rs#L5827-L5863","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet receipt = registry.followup_child(&agent_ref, text)?;\n// after\nlet status = registry.status_of(&agent_ref);\nlet receipt = if matches!(status, SubAgentStatus::Running | SubAgentStatus::Interrupted(_)) {\n    registry.followup_child(&agent_ref, text)?\n} else {\n    registry.continue_agent(&agent_ref, text)? // resume path for terminal/interrupted children\n};","handlingStrategy":"validation","validationCode":"let status = registry.status_of(&agent_ref);\nif matches!(status, SubAgentStatus::Completed | SubAgentStatus::Failed(_) | SubAgentStatus::Cancelled | SubAgentStatus::BudgetExhausted) {\n    // terminal: use continue_agent or spawn a new child instead\n}","typeGuard":"fn is_resumable(status: &SubAgentStatus) -> bool {\n    !matches!(status, SubAgentStatus::Completed | SubAgentStatus::Failed(_) | SubAgentStatus::Cancelled | SubAgentStatus::BudgetExhausted)\n}","tryCatchPattern":"match registry.followup_child(&agent_ref, text) {\n    Ok(receipt) => /* delivered or queued */,\n    Err(e) if e.to_string().contains(\"cannot resume\") => {\n        let _ = registry.continue_agent(&agent_ref, text); // fall back to resume path\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["subagent","agent-lifecycle","invalid-state-transition","rust"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}