Hmbown/CodeWhale · error

Refusing {action} on self (agent_id '{agent_id}'); child coo

Error message

Refusing {action} on self (agent_id '{agent_id}'); child coordination authority is limited to strict descendants.

What it means

Thrown by the sub-agent manager's coordination guard `ensure_caller_controls_descendant` (crates/tui/src/tools/subagent/mod.rs:6769) when a child agent attempts a control action (stop, pause, status, etc.) on itself. A child's coordination authority is deliberately limited to strict descendants; the guard first resolves the target ref, then refuses when the resolved agent_id equals the non-root caller id. Root callers (None, empty, or "root") bypass this check entirely, so it only fires for child-agent callers.

Source

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

    /// descendant of the calling agent. Root registries carry no caller id
    /// (or the literal `root`) and retain authority over every child. This
    /// prevents a child from messaging, waking, or interrupting a sibling or
    /// ancestor and then claiming the ownership that target released.
    pub(super) fn ensure_caller_controls_descendant(
        &self,
        agent_ref: &str,
        caller_agent_id: Option<&str>,
        action: &str,
    ) -> Result<String> {
        let agent_id = self.resolve_agent_ref(agent_ref)?;
        let Some(caller) = caller_agent_id
            .map(str::trim)
            .filter(|caller| !caller.is_empty() && *caller != "root")
        else {
            return Ok(agent_id);
        };
        if caller == agent_id {
            return Err(anyhow!(
                "Refusing {action} on self (agent_id '{agent_id}'); child coordination authority is limited to strict descendants."
            ));
        }

        let mut cursor = agent_id.clone();
        let mut visited = std::collections::HashSet::new();
        while visited.insert(cursor.clone()) {
            let Some((_, record)) = self.worker_record_by_ref(&cursor) else {
                break;
            };
            let Some(parent_ref) = record
                .parent_run_id
                .as_deref()
                .or(record.spec.parent_run_id.as_deref())
            else {
                break;
            };
            if parent_ref == caller {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Filter the caller's own agent_id out of any enumerated agent list before issuing control actions.
  2. If the action genuinely must apply to that agent, issue it from the root session (caller None/"root"), where the guard passes.
  3. Target only refs of agents this child spawned, or spawn the target under this child first so it is a strict descendant.
  4. For self-termination, use the child's normal completion path (final report / finish) instead of a control action.

Example fix

// before (inside a child agent, caller_agent_id = Some(child_id))
manager.ensure_caller_controls_descendant("self", caller_agent_id, "stop")?; // Err 1200

// after
let target = manager.resolve_agent_ref(agent_ref)?;
if caller_agent_id == Some(target.as_str()) { return Ok(()); } // skip self
manager.ensure_caller_controls_descendant(&target, caller_agent_id, "stop")?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_self_target(resolved: &str, caller: Option<&str>) -> bool {
    caller
        .map(str::trim)
        .is_some_and(|c| !c.is_empty() && c != "root" && c == resolved)
}
// before calling: if is_self_target(&manager.resolve_agent_ref(ref)?, caller) { skip }

Type guard

fn safe_control_target(resolved: &str, caller: Option<&str>) -> bool {
    match caller.map(str::trim) {
        Some(c) if !c.is_empty() && c != "root" => c != resolved, // not self
        _ => true, // root caller: guard bypassed
    }
}

Try / catch

match manager.ensure_caller_controls_descendant(&agent_ref, caller, action) {
    Err(e) if e.to_string().contains("on self") => { /* skip self-targeted action, not fatal */ }
    other => other?,
}

Prevention

When it happens

Trigger: A sub-agent whose caller_agent_id is a non-empty, non-"root" id calls a control API with an agent_ref that `resolve_agent_ref` maps to the caller's own agent_id — e.g. passing "self", "me", an alias, or the literal id equal to the caller.

Common situations: Orchestrator prompts like "manage all your workers" where the child enumerates agents including itself; agent_ref aliases that resolve back to the caller; root-session commands copied into a child prompt and replayed verbatim.

Related errors


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