Hmbown/CodeWhale · error · anyhow::Error
Sub-agent requested unavailable tools
Error message
Sub-agent requested unavailable tools: {} What it means
When a sub-agent is constructed, the tool registry reports which allowed tools are unavailable in this environment (unavailable_allowed_tools). If any requested-but-unavailable tool exists, the child is refused at construction with this error listing them, instead of silently running with a missing tool surface. This is the fail-loud companion to 'misconfiguration fails loud'.
Solutions
- Remove the unavailable tool names from the sub-agent's allow-list/agent config
- Enable the feature or install the dependency that provides the named tool
- Check tool renames between versions and update the config to the current tool names
- Verify the environment (PATH, feature flags) the child inherits actually provides the requested tools
Example fix
// before: allow-list names a tool this build lacks allow = ["read", "browser"] // browser unavailable here // after allow = ["read", "search"] // tools present in this environment
Defensive patterns
Strategy: validation
Validate before calling
let unavailable = registry.unavailable_allowed_tools();
if !unavailable.is_empty() {
eprintln!("fix allow-list before spawn: {}", unavailable.join(", "));
return Err(anyhow!("remove or enable: {}", unavailable.join(", ")));
} Try / catch
match spawn_subagent(config) {
Err(e) if e.to_string().contains("requested unavailable tools") => {
let fixed = strip_unavailable(config);
spawn_subagent(fixed)
}
other => other,
} Prevention
- Validate tool allow-lists against the current environment before spawn
- Remove deprecated tool names from configs when upgrading
- Test sub-agent configs in CI on the same environment profile used in production
When it happens
Trigger: Creating/launching a sub-agent whose tool configuration (allow-list, agent-type default tools) names tools that cannot be provided in the current environment or build — e.g. a tool gated behind a missing binary, disabled feature, or removed surface.
Common situations: Config written on a machine with extra tools installed being reused on a leaner environment; a deprecated/renamed tool still listed in an allow-list; a feature flag disabled for the child's runtime.
Understand the failure class
Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.
Related errors
- Custom sub-agent requires a non-empty allowed_tools list
- Tool could not enter the bounded child toolbox; use…
- Tool is not in this child's catalog
- Tool is not in this child's policy-filtered catalog
- Tool not allowed for this sub-agent
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/936317f95b657c04.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/subagent/mod.rs:13049
agent_type.clone(),
agent_id.clone(),
assignment
.role
.as_deref()
.filter(|role| !role.trim().is_empty())
.unwrap_or(agent_type.as_str())
.to_string(),
allowed_tools.clone(),
// This agent's own list, allocated by `child_runtime()` (#4810). It is
// writable by this agent alone: neither the parent nor a sibling holds
// this `Arc`, so `work_update` here cannot reach the parent's Work
// checklist or work graph.
runtime.todos.clone(),
Arc::new(Mutex::new(PlanState::default())),
);
let unavailable_tools = tool_registry.unavailable_allowed_tools();
if !unavailable_tools.is_empty() {
return Err(anyhow!(
"Sub-agent requested unavailable tools: {}",
unavailable_tools.join(", ")
));
}
let tool_catalog = tool_registry.deferred_catalog_for_model(&agent_type);
let mut tool_surface = SubAgentToolSurface::new(tool_catalog, &[]);
let mut steps = 0;
let mut final_result: Option<String> = None;
let mut pending_inputs: VecDeque<SubAgentInput> = VecDeque::new();
let mut latest_checkpoint: Option<SubAgentCheckpoint> = None;
let mut tokens_used: u64 = 0;
let mut terminal_failure_reason: Option<String> = None;
let mut budget_failure_reason: Option<String> = None;
let mut handback_note: Option<String> = None;
// Distinguish a real "the model chose to stop" exit from an explicitly
// configured step-cap exit. The normal loop is unbounded (max_steps == 0).
let mut stopped_naturally = false;
// #6015: every child worker runs the same typed-denial no-progress guardView on GitHub (pinned to 73e0f67d83)