Hmbown/CodeWhale · error

Refusing to interrupt root. agents/interrupt fails closed on

Error message

Refusing to interrupt root. agents/interrupt fails closed on the root session.

What it means

interrupt_child hard-refuses the literal reference "root" (case-insensitive, whitespace-trimmed): the agents/interrupt tool fails closed on the root session because interrupting root would tear down the orchestrating session itself, which the sub-agent API must never do. Only actual children are interruptible; stopping the root is the host UI's job (e.g., the user's interrupt key).

Source

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

            resume_prompt,
            assignment,
            allowed_tools,
            options,
        )?;
        self.resume_targets
            .insert(agent_id, resumed.agent_id.clone());
        Ok(resumed)
    }

    /// Interrupt a child, preserve checkpoint, fail closed on root/self.
    pub fn interrupt_child(
        &mut self,
        agent_ref: &str,
        caller_agent_id: Option<&str>,
        reason: String,
    ) -> Result<(SubAgentResult, SubAgentResult)> {
        if agent_ref.trim().eq_ignore_ascii_case("root") {
            return Err(anyhow!(
                "Refusing to interrupt root. agents/interrupt fails closed on the root session."
            ));
        }
        let agent_id = self.resolve_agent_ref(agent_ref)?;
        self.ensure_caller_controls_descendant(&agent_id, caller_agent_id, "agents/interrupt")?;

        let prior = self.get_result_by_ref(&agent_id)?;
        if prior.status != SubAgentStatus::Running
            || self
                .agents
                .get(&agent_id)
                .is_some_and(|agent| agent.completion_claimed)
        {
            return Ok((prior.clone(), prior));
        }

        // Build a continuable checkpoint from the latest stored checkpoint or a
        // minimal placeholder so interrupt never drops recoverability silently.

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Target a real child: pass an agent_id or session name obtained from the spawn receipt or manager.list()
  2. To stop the whole run, use the host application's own root interrupt (UI interrupt action), not the agents tool
  3. Validate refs before the call and reject the root alias in your own orchestration layer with a clearer message

Example fix

// before
manager.interrupt_child("root", None, reason)?;

// after
let targets: Vec<String> = manager.list().into_iter()
    .filter(|s| matches!(s.status, SubAgentStatus::Running))
    .map(|s| s.agent_id)
    .collect();
for id in targets {
    manager.interrupt_child(&id, None, reason.clone())?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Reject the root alias before it reaches the manager.
anyhow::ensure!(
    !agent_ref.trim().eq_ignore_ascii_case("root"),
    "agents/interrupt cannot target root; pass a child agent_id or session name"
);

Try / catch

On 'Refusing to interrupt root', map the action to the host's own root interrupt (or interrupt each running child by id); the error is a permanent refusal, never retried as-is.

Prevention

When it happens

Trigger: Calling interrupt_child("root", caller, reason) — the guard fires before any resolution; a model confusing the root session with a worker and emitting agent_ref="root"; template code that passes a fixed "root" target.

Common situations: LLM tool-calls with agent_ref="root" because the prompt mentions the root session; user commands like 'interrupt everything' being translated to the root ref by glue code; copy-pasted examples using "root" as a placeholder.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/0e92895035521aa7. Report an issue: GitHub.