Hmbown/CodeWhale · error · anyhow::Error

Custom sub-agent requires a non-empty allowed_tools list

Error message

Custom sub-agent requires a non-empty allowed_tools list

What it means

When a custom (`FleetRole::Custom`) sub-agent declares an allowed_tools list, every entry is trimmed and deduplicated; if nothing survives, the list is empty and configuration is rejected. A custom agent must name at least one tool explicitly — unlike built-in roles, it gets no default toolset.

Solutions

  1. Add at least one real tool name to allowed_tools
  2. Remove surrounding whitespace/separator-only entries from the list
  3. Use a built-in role instead of Custom if the default tool inheritance is desired

Example fix

// before
spawn_subagent(type: "custom", allowed_tools: ["  ", "read", "read"])
// after
spawn_subagent(type: "custom", allowed_tools: ["read", "grep"])
Defensive patterns

Strategy: validation

Validate before calling

const cleaned = [...new Set((allowedTools ?? []).map(t => t.trim()).filter(Boolean))];
if (agentType === 'custom' && cleaned.length === 0) throw new Error('allowed_tools must be non-empty for custom agents');

Type guard

const hasAllowedTools = (list) => Array.isArray(list) && list.map(s => String(s).trim()).filter(Boolean).length > 0;

Prevention

When it happens

Trigger: Creating a custom sub-agent with `allowed_tools: []`, only whitespace/comma entries like `" "` or `",,"`, or duplicate names that dedupe down to zero.

Common situations: Config typo leaving allowed_tools blank; building the list programmatically from an empty source; copying a config template with a placeholder empty list.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/0f21d828f0cd724c. Report an issue: GitHub.

Appendix: source

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

///
/// `allow_shell = false` no longer narrows the tool LIST — the child's
/// registry simply doesn't register shell tools, which has the same
/// effect without papering over the parent's choice with a deny-list.
fn build_allowed_tools(
    agent_type: &FleetRole,
    explicit_tools: Option<Vec<String>>,
    _allow_shell: bool,
) -> Result<Option<Vec<String>>> {
    if let Some(tools) = explicit_tools {
        let mut deduped = Vec::new();
        for tool in tools {
            let name = tool.trim();
            if !name.is_empty() && !deduped.iter().any(|existing: &String| existing == name) {
                deduped.push(name.to_string());
            }
        }
        if matches!(agent_type, FleetRole::Custom) && deduped.is_empty() {
            return Err(anyhow!(
                "Custom sub-agent requires a non-empty allowed_tools list"
            ));
        }
        return Ok(Some(deduped));
    }

    if matches!(agent_type, FleetRole::Custom) {
        return Err(anyhow!(
            "Custom sub-agent requires a non-empty allowed_tools list"
        ));
    }

    // Default: full registry inheritance from the parent. The child sees every
    // tool the parent has, including the sub-agent management family. The
    // registry execution guard still blocks approval-gated tools unless the
    // parent runtime is auto-approved.
    Ok(None)
}

View on GitHub (pinned to 73e0f67d83)