{"record":{"id":"1b5f56bfced62653","repo":"Hmbown/CodeWhale","slug":"cannot-resume-agent-agent-id-status-is","errorCode":null,"errorMessage":"Cannot resume agent {agent_id}: status is {} ({})","messagePattern":"Cannot resume agent (.+?): status is (.+?) \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":5683,"sourceCode":"            claim,\n            preserved_profile,\n            child_route,\n        ) = {\n            let agent = self\n                .agents\n                .get(&agent_id)\n                .ok_or_else(|| anyhow!(\"Agent {agent_id} not found\"))?;\n            let resumable = match policy {\n                ResumePolicy::InterruptedOnly => {\n                    matches!(agent.status, SubAgentStatus::Interrupted(_))\n                }\n                ResumePolicy::InterruptedOrCompleted => matches!(\n                    agent.status,\n                    SubAgentStatus::Interrupted(_) | SubAgentStatus::Completed\n                ),\n            };\n            if !resumable {\n                return Err(anyhow!(\n                    \"Cannot resume agent {agent_id}: status is {} ({})\",\n                    subagent_status_name(&agent.status),\n                    policy.describe()\n                ));\n            }\n            let checkpoint = agent\n                .checkpoint\n                .as_ref()\n                .filter(|cp| cp.continuable && !cp.messages.is_empty())\n                .ok_or_else(|| {\n                    let continuable = agent.checkpoint.as_ref().is_some_and(|cp| cp.continuable);\n                    let messages = agent\n                        .checkpoint\n                        .as_ref()\n                        .map(|cp| cp.messages.len())\n                        .unwrap_or(0);\n                    anyhow!(\n                        \"Agent {agent_id} has no continuable checkpoint to resume from (continuable={continuable}, messages={messages})\"","sourceCodeStart":5665,"sourceCodeEnd":5701,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/subagent/mod.rs#L5665-L5701","documentation":"resume_from_checkpoint_with_policy gates on a ResumePolicy: InterruptedOnly accepts only Interrupted agents, InterruptedOrCompleted also accepts Completed. When the agent's actual status does not satisfy the requested policy, the resume is refused with the status name and the policy description. The policy exists so that, for example, an interrupted-only resume path can never silently resurrect an already-completed agent.","triggerScenarios":"Calling resume with ResumePolicy::InterruptedOnly on a Completed agent; calling resume on Failed, Cancelled, or BudgetExhausted agents (no policy accepts them); a status race where the agent transitions (e.g., interrupted -> completed) between the caller's decision and the resume call.","commonSituations":"Orchestration code hardcoding InterruptedOnly for all resumes; user-initiated 'continue' racing an agent that finishes on its own; mixing up the interrupt-resume path with the completion-continuation path after an API change.","solutions":["Derive the policy from the agent's current status instead of hardcoding it (Interrupted -> InterruptedOnly; Completed -> InterruptedOrCompleted)","Prefer the higher-level continue_child_from_user, which selects the correct policy per status","Re-check status with get_result_by_ref right before resuming and treat a mismatch as a signal to re-plan, not to retry the same policy"],"exampleFix":"// before\nlet resumed = manager.resume_from_checkpoint_with_policy(\n    handle, runtime, &id, &text, ResumePolicy::InterruptedOnly)?;\n\n// after\nlet snap = manager.get_result_by_ref(&id)?;\nlet policy = match snap.status {\n    SubAgentStatus::Interrupted(_) => ResumePolicy::InterruptedOnly,\n    SubAgentStatus::Completed => ResumePolicy::InterruptedOrCompleted,\n    other => anyhow::bail!(\"status {other:?} cannot resume\"),\n};\nlet resumed = manager.resume_from_checkpoint_with_policy(\n    handle, runtime, &id, &text, policy)?;","handlingStrategy":"type-guard","validationCode":"// Choose the policy from observed status instead of a constant.\nlet snap = manager.get_result_by_ref(&agent_id)?;\nlet policy = match snap.status {\n    SubAgentStatus::Interrupted(_) => ResumePolicy::InterruptedOnly,\n    SubAgentStatus::Completed => ResumePolicy::InterruptedOrCompleted,\n    other => anyhow::bail!(\"status {other:?} has no resume policy\"),\n};","typeGuard":"fn policy_allows(policy: ResumePolicy, status: &SubAgentStatus) -> bool {\n    match policy {\n        ResumePolicy::InterruptedOnly =>\n            matches!(status, SubAgentStatus::Interrupted(_)),\n        ResumePolicy::InterruptedOrCompleted =>\n            matches!(status, SubAgentStatus::Interrupted(_) | SubAgentStatus::Completed),\n    }\n}","tryCatchPattern":"Catch 'Cannot resume agent' and re-read status; if it transitioned (e.g., interrupted -> completed), recompute the policy and retry once — further mismatches mean the agent is dead.","preventionTips":["Never hardcode InterruptedOnly for generic resume flows","Prefer continue_child_from_user, which owns policy selection, over calling the policy seam directly","Re-check status adjacent to the resume call to shrink race windows"],"tags":["subagent","rust","resume","policy","invalid-state-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}