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

  1. Remove the unavailable tool names from the sub-agent's allow-list/agent config
  2. Enable the feature or install the dependency that provides the named tool
  3. Check tool renames between versions and update the config to the current tool names
  4. 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

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


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 guard

View on GitHub (pinned to 73e0f67d83)