{"record":{"id":"19ee0ba6fdeaab8e","repo":"Hmbown/CodeWhale","slug":"invalid-agent-continuation-lineage-cycle","errorCode":null,"errorMessage":"Invalid agent continuation lineage: cycle","messagePattern":"Invalid agent continuation lineage: cycle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":5997,"sourceCode":"    ) -> Result<ParentMailReceipt> {\n        let agent_id = self.resolve_agent_ref_for_session(active_session_id, agent_ref)?;\n        self.followup_child(&agent_id, text)\n    }\n\n    /// Follow the persisted continuation chain without treating deliberate\n    /// `start + resume_from` forks as continuation targets. Every hop retains\n    /// the original root conversation; malformed or cyclic state fails closed.\n    fn continuation_target(&self, agent_id: &str) -> Result<String> {\n        let source = self\n            .agents\n            .get(agent_id)\n            .ok_or_else(|| anyhow!(\"Agent not found\"))?;\n        let owner = &source.owner_session_id;\n        let mut target = agent_id.to_string();\n        let mut seen = HashSet::new();\n        while let Some(next) = self.resume_targets.get(&target) {\n            if !seen.insert(target.clone()) {\n                return Err(anyhow!(\"Invalid agent continuation lineage: cycle\"));\n            }\n            let successor = self\n                .agents\n                .get(next)\n                .ok_or_else(|| anyhow!(\"Agent continuation target is no longer retained\"))?;\n            if owner.is_empty() || successor.owner_session_id != *owner {\n                return Err(anyhow!(\n                    \"Agent continuation target is outside the active session\"\n                ));\n            }\n            target.clone_from(next);\n        }\n        Ok(target)\n    }\n\n    fn continuation_source(&self, agent_id: &str) -> Option<String> {\n        self.resume_targets.iter().find_map(|(source, target)| {\n            (target == agent_id && self.continuation_target(source).is_ok()).then(|| source.clone())","sourceCodeStart":5979,"sourceCodeEnd":6015,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/subagent/mod.rs#L5979-L6015","documentation":"Thrown by `continuation_target` while walking the `resume_targets` chain: if the walk revisits an id already seen (HashSet insert returns false), the lineage contains a cycle and fails closed. A cyclic lineage means resume bookkeeping was corrupted, so no continuation target can be trusted.","triggerScenarios":"Calling continue/follow-up on an agent whose `resume_targets` map forms a loop — e.g. record A points to B and B (directly or transitively) points back to A — so the `while let Some(next) = self.resume_targets.get(&target)` walk never reaches an agent without a successor.","commonSituations":"Corrupted persisted resume state loaded from disk; a bug that sets resume_targets bidirectionally when forking continuations; manual editing or partial writes of checkpoint state; version changes to the resume-target format leaving stale entries.","solutions":["Inspect and clear the offending `resume_targets` entries so the chain terminates at the real continuation root.","Delete the corrupted persisted subagent state and let the registry rebuild lineage from checkpoints.","Reproduce with a dump of the resume_targets chain to find the cycle edge and fix the code that wrote it.","As a workaround, start a fresh child agent instead of continuing the one with the corrupt lineage."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"let mut seen = std::collections::HashSet::new();\nlet mut cur = agent_id.to_string();\nwhile let Some(next) = registry.resume_target_of(&cur) {\n    if !seen.insert(cur.clone()) {\n        anyhow::bail!(\"corrupt resume lineage detected before continue\");\n    }\n    cur = next;\n}","typeGuard":null,"tryCatchPattern":"match registry.continuation_target(agent_id) {\n    Ok(t) => t,\n    Err(e) if e.to_string().contains(\"cycle\") => {\n        registry.clear_resume_targets(agent_id); // fall back to forking from the root\n        agent_id.to_string()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never write resume_targets bidirectionally when forking continuations.","Validate persisted resume lineage on load (detect cycles at restore time).","Avoid hand-editing or partially writing subagent checkpoint state.","On format changes, migrate or discard old resume_targets rather than loading them raw."],"tags":["subagent","cycle-detection","corrupt-state","rust"],"backgroundTag":"internal-invariant-violation","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"}