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
- Add at least one real tool name to allowed_tools
- Remove surrounding whitespace/separator-only entries from the list
- 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
- Validate custom agent configs at load time
- Reject whitespace-only or duplicate-only tool lists early
- Prefer built-in roles when default tool inheritance is wanted
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
- A pinned task provider requires an explicit model
- agent profile reasoning_effort must be one of: inherit…
- Checkpoint continuation requires a source agent
- Codewhale credentials path must be a directory
- deliverable is outside the worker write scope; declare an…
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)