Hmbown/CodeWhale · error

Invalid agent continuation lineage: cycle

Error message

Invalid agent continuation lineage: cycle

What it means

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.

Solutions

  1. Inspect and clear the offending `resume_targets` entries so the chain terminates at the real continuation root.
  2. Delete the corrupted persisted subagent state and let the registry rebuild lineage from checkpoints.
  3. Reproduce with a dump of the resume_targets chain to find the cycle edge and fix the code that wrote it.
  4. As a workaround, start a fresh child agent instead of continuing the one with the corrupt lineage.
Defensive patterns

Strategy: fallback

Validate before calling

let mut seen = std::collections::HashSet::new();
let mut cur = agent_id.to_string();
while let Some(next) = registry.resume_target_of(&cur) {
    if !seen.insert(cur.clone()) {
        anyhow::bail!("corrupt resume lineage detected before continue");
    }
    cur = next;
}

Try / catch

match registry.continuation_target(agent_id) {
    Ok(t) => t,
    Err(e) if e.to_string().contains("cycle") => {
        registry.clear_resume_targets(agent_id); // fall back to forking from the root
        agent_id.to_string()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/19ee0ba6fdeaab8e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/subagent/mod.rs:5997

    ) -> Result<ParentMailReceipt> {
        let agent_id = self.resolve_agent_ref_for_session(active_session_id, agent_ref)?;
        self.followup_child(&agent_id, text)
    }

    /// Follow the persisted continuation chain without treating deliberate
    /// `start + resume_from` forks as continuation targets. Every hop retains
    /// the original root conversation; malformed or cyclic state fails closed.
    fn continuation_target(&self, agent_id: &str) -> Result<String> {
        let source = self
            .agents
            .get(agent_id)
            .ok_or_else(|| anyhow!("Agent not found"))?;
        let owner = &source.owner_session_id;
        let mut target = agent_id.to_string();
        let mut seen = HashSet::new();
        while let Some(next) = self.resume_targets.get(&target) {
            if !seen.insert(target.clone()) {
                return Err(anyhow!("Invalid agent continuation lineage: cycle"));
            }
            let successor = self
                .agents
                .get(next)
                .ok_or_else(|| anyhow!("Agent continuation target is no longer retained"))?;
            if owner.is_empty() || successor.owner_session_id != *owner {
                return Err(anyhow!(
                    "Agent continuation target is outside the active session"
                ));
            }
            target.clone_from(next);
        }
        Ok(target)
    }

    fn continuation_source(&self, agent_id: &str) -> Option<String> {
        self.resume_targets.iter().find_map(|(source, target)| {
            (target == agent_id && self.continuation_target(source).is_ok()).then(|| source.clone())

View on GitHub (pinned to 73e0f67d83)