{"record":{"id":"185ef633b6a4f1e6","repo":"Hmbown/CodeWhale","slug":"refusing-action-from-agent-caller-to-agent","errorCode":null,"errorMessage":"Refusing {action} from agent '{caller}' to '{agent_id}'; a child may control only its own descendants.","messagePattern":"Refusing (.+?) from agent '(.+?)' to '(.+?)'; a child may control only its own descendants\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":6813,"sourceCode":"                .parent_run_id\n                .as_deref()\n                .or(record.spec.parent_run_id.as_deref())\n            else {\n                break;\n            };\n            if parent_ref == caller {\n                return Ok(agent_id);\n            }\n            if parent_ref == \"root\" {\n                break;\n            }\n            let Some((parent_id, _)) = self.worker_record_by_ref(parent_ref) else {\n                break;\n            };\n            cursor = parent_id;\n        }\n\n        Err(anyhow!(\n            \"Refusing {action} from agent '{caller}' to '{agent_id}'; a child may control only its own descendants.\"\n        ))\n    }\n\n    pub(super) fn ensure_caller_controls_descendant_for_session(\n        &self,\n        active_session_id: &str,\n        agent_ref: &str,\n        caller_agent_id: Option<&str>,\n        action: &str,\n    ) -> Result<String> {\n        let agent_id = self.resolve_agent_ref_for_session(active_session_id, agent_ref)?;\n        if let Some(caller) = caller_agent_id\n            .map(str::trim)\n            .filter(|caller| !caller.is_empty() && *caller != \"root\")\n        {\n            self.resolve_agent_ref_for_session(active_session_id, caller)?;\n        }","sourceCodeStart":6795,"sourceCodeEnd":6831,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/subagent/mod.rs#L6795-L6831","documentation":"The sibling check of error 1200 in `ensure_caller_controls_descendant`: after passing the self-check, the guard walks the target's parent chain (worker_record_by_ref -> parent_run_id) looking for the caller. If it reaches \"root\" or an unknown parent record without ever meeting the caller, the action is refused — a child may control only its own strict descendants. This blocks lateral (sibling-to-sibling) and upward (child-to-ancestor) control.","triggerScenarios":"A child agent issues a control action targeting a sibling spawned by the same parent, one of its own ancestors, or an unrelated agent; also fires when the parent_run_id chain is broken because intermediate worker records were pruned or expired.","commonSituations":"Fan-out orchestrators where one child tries to coordinate its peers; stale agent refs after parent records were reaped mid-run; root-level fleet commands replayed inside a child; adoption/re-parenting logic that breaks parent_run_id links.","solutions":["Run cross-branch control actions from the root session instead of a child.","Spawn the target agent under the calling child so it becomes a strict descendant.","Before acting, verify the target's parent_run_id chain actually reaches the caller (no pruned records).","If records were reaped, restart the affected branch from root rather than working around the guard."],"exampleFix":"// before (child tries to stop a sibling)\nmanager.ensure_caller_controls_descendant(sibling_ref, Some(my_id), \"stop\")?; // Err 1201\n\n// after: route cross-branch control through root\nmanager.ensure_caller_controls_descendant(sibling_ref, None, \"stop\")?; // root caller","handlingStrategy":"validation","validationCode":"// Verify the target is a strict descendant before acting:\n// walk parent_run_id links from target upward; the caller must appear before \"root\".\nfn caller_owns(manager: &SubagentManager, target: &str, caller: &str) -> bool {\n    let mut cursor = target.to_string();\n    let mut seen = std::collections::HashSet::new();\n    while seen.insert(cursor.clone()) {\n        let Some((_, rec)) = manager.worker_record_by_ref(&cursor) else { return false };\n        match rec.parent_run_id.as_deref() {\n            Some(p) if p == caller => return true,\n            Some(\"root\") | None => return false,\n            Some(p) => cursor = p.to_string(),\n        }\n    }\n    false\n}","typeGuard":"fn is_strict_descendant(manager: &SubagentManager, target: &str, caller: &str) -> bool {\n    caller_owns(manager, target, caller)\n}","tryCatchPattern":"match manager.ensure_caller_controls_descendant(&agent_ref, caller, action) {\n    Err(e) if e.to_string().contains(\"only its own descendants\") => {\n        // re-route: either run from root or skip; do not retry unchanged\n    }\n    other => other?,\n}","preventionTips":["Children should only ever name refs they received from their own spawn results.","Have the root session own cross-branch coordination.","Watch for pruned worker records: a broken parent chain also refuses."],"tags":["subagent","authorization","descendant-check","rust","codewhale"],"backgroundTag":"agent-hierarchy-permission-denied","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}