Claude Code Subagent allowed-tools: Syntax, Examples, Fixes
Originally published at Unattended . Claude Code subagents don't have an allowed-tools field. The key you want in a subagent's .md frontmatter is tools . It's an allowlist, written as a comma-separated string like tools: Read, Grep, Glob or as a YAML list. disallowedTools is the matching denylist.

Posted on Nov 1 • Originally published at en.aobaworks.com
Claude Code subagents don't have an allowed-tools field. The key you want in a subagent's.md frontmatter is tools. It's an allowlist, written as a comma-separated string like tools: Read, Grep, Glob or as a YAML list. disallowedTools is the matching denylist. allowed-tools belongs to Skills, and it does something else: it pre-approves tools for one turn and restricts nothing. If you leave tools out, the subagent " Inherits every tool available to subagents if omitted. "
The best place to start depends on whether you want a safe default fast, a dependable daily setup, or the lowest token cost.
The reason for each row: an explicit read-only list can't fail in a dangerous way. A Bash deny rule blocks particular commands without taking Bash away. A server wildcard keeps the list short. A denylist is the least work when you only fear one or two tools. Inline MCP keeps tool definitions out of the main context. Hooks still run when a frontmatter list turns out wrong.
In Claude Code subagents, the "allowed-tools" job is done by the tools frontmatter key, an allowlist that limits which tools that subagent can call. Many people search for allowed-tools because they've seen it in Skills or the Agent SDK, so the three names are worth separating before you write any YAML.
The Skills documentation says the Skills allowed-tools grant clears when you send your next message, and that it does not restrict which tools are available. The Agent SDK permissions page treats allowedTools as approval too. Put allowed-tools: in a subagent file and you get neither behavior: it isn't a supported subagent field, so the subagent inherits everything.
Here is a minimal, valid subagent. Save it as.claude/agents/code-reviewer.md (example):
--- name: code-reviewer description: Reviews diffs for bugs and style issues. Use after code changes. tools: Read, Grep, Glob model: sonnet --- You are a code reviewer. Read the changed files, report problems with file and line, and never modify files. Enter fullscreen mode Exit fullscreen mode Some official limits are worth knowing (as of October 2026, sub-agents docs ). name can be up to 256 characters and can't contain:. Claude Code warns at startup when the combined descriptions of your custom subagents go over 15,000 tokens. By default, 20 subagents running at once is the ceiling, and the next spawn fails with Concurrent subagent limit reached.
Claude Code's subagent tools field takes exact tool names, either as a comma-separated string or a YAML list. The documented wildcards are for MCP servers, such as mcp__github__*.
tools: Read, Grep, Glob, Bash Enter fullscreen mode Exit fullscreen mode YAML list form (same meaning):
tools: - Read - Grep - Glob - Bash Enter fullscreen mode Exit fullscreen mode Every tool from one MCP server, plus Read:
tools: Read, mcp__github__* Enter fullscreen mode Exit fullscreen mode Everything inherited except one MCP server:
disallowedTools: mcp__github Enter fullscreen mode Exit fullscreen mode No MCP tools at all, built-ins kept:
disallowedTools: mcp__* Enter fullscreen mode Exit fullscreen mode Those three MCP patterns come straight from the sub-agents table. One MCP tool is named mcp__<server>__<tool>, the same format hook matchers use, for example mcp__memory__create_entities.
For one-off or scripted runs, the --agents flag takes JSON, with tools as an array ( sub-agents docs ):
claude --agents '{ "code-reviewer": { "description": "Expert code reviewer. Use proactively after code changes.", "prompt": "You are a senior code reviewer.", "tools": ["Read", "Grep", "Glob", "Bash"], "model": "sonnet" } }' Enter fullscreen mode Exit fullscreen mode In non-interactive mode you can pass a file path instead, such as claude -p --agents./agents.json "Review my changes". Interactive sessions refuse a file path ( sub-agents docs ).
For a Claude Code subagent that runs without you watching, list its tools explicitly. Inheriting is fine for interactive helpers you supervise. If you leave tools out, the subagent picks up every built-in and MCP tool the main conversation has, after a couple of filters. Listing tools gives you predictable behavior, and in exchange you have to update the list when the job changes.
When both fields are set: " If both are set, disallowedTools is applied first, then tools is resolved against the remaining pool. " A tool that appears in both lists ends up removed.
Two inheritance details tend to catch people:
A practical rule: if the subagent can run when you're not at the keyboard, such as from cron, CI, or a background session, write out its tools. If you start it yourself and watch the output, inheriting is acceptable.
A Claude Code subagent can use any built-in or connected MCP tool, except a few that are always withheld. Built-in names come from the tools reference, which says they " are the exact strings you use in permission rules, subagent tool lists, and hook matchers. "
Subagents run in the background by default. A background subagent keeps every MCP tool but only these built-in tools (as of October 2026, sub-agents docs ):
The list also includes SubagentHandback for a subagent that reports through it, and Agent, which follows the depth limit. Before v2.1.280, background subagents couldn't use LSP ( sub-agents docs ).
AskUserQuestion, EndConversation, EnterPlanMode, ScheduleWakeup, WaitForMcpServers, Workflow, ExitPlanMode (unless permissionMode is plan ), and Agent once the subagent hits the depth limit. Subagents can spawn their own subagents "up to three layers below the main conversation" by default. You change that limit with CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH.
That affects how you design the job. A subagent can't stop and ask you a question, so its prompt has to contain everything it needs. And because the background filter drops some tools silently, " the same definition can resolve to different tools in the foreground and the background. "
When a Claude Code subagent lacks a tool it needs, it either can't launch or does a weaker version of the task. When it has more tools than it needs, it can change things you never asked it to touch. Here are the failures you're most likely to hit, with fixes.
If nothing in tools resolves to a real tool, because of typos, lowercase names, or tools subagents can't have, Claude Code "usually refuses to launch the subagent," and the Agent tool returns an error naming the entries it couldn't resolve ( sub-agents docs; errors page ). The error text contains "spawned with zero tools." Fix: check capitalization against the tools reference, and remove anything from the withheld list above.
The error only appears when the list resolves to nothing. Suppose you write tools: Read, edit, Bash (example). edit doesn't resolve, so the subagent starts with Read and Bash. It may then try to make changes through shell commands, or report that it couldn't edit. Fix: after any edit to the list, ask the subagent to name the tools it has before the first real run.
A tool that isn't on the background keep-list gets removed with no error. A definition that works when you watch it in the foreground can then break overnight. Fix: build your lists only from the background table above, or set background: false for interactive use. When a cron run stops halfway, also read /why-claude-code-stops-mid-automation-and-how-to-fix-it/.
That key isn't a subagent field, so the subagent inherits every tool, and the safety limit you thought you'd set isn't there. Fix: rename the key to tools.
disallowedTools: Bash(rm *) takes away the whole Bash tool, so test runs fail too. Fix: move the pattern to permissions.deny in.claude/settings.json.
For example, a "reviewer" with inherited Bash and Write might run a formatter or rewrite files while it reviews. In acceptEdits or auto mode, nothing asks you first. Fix: use an explicit read-only list, and add a hook (next sections) that denies writes from that agent type. For broader rule sets you can paste in, see /ai-agent-guardrails-example-copy-paste-claude-code-rules/.
Connect a Slack server, and every subagent without a tools list can now post messages. Fix: add disallowedTools: mcp__* to agents that should stay local, or define the server inline with mcpServers so only one agent connects to it.
Claude Code hooks are commands (or HTTP calls, MCP tools, prompts, or agents) that run automatically at fixed lifecycle points. They give you a second layer of enforcement that keeps working even when a subagent's tool list is wrong. The tools field decides what a subagent can try. A hook can inspect each attempt as it happens and block it.
Hooks reach subagents in two ways ( sub-agents docs ):
Each hook gets JSON input that includes agent_id and agent_type (the subagent's name ), so one settings-level script can apply different rules to different agents. SubagentStart and SubagentStop also accept a matcher on the agent's name. Plugin-scoped names like my-plugin:db-agent contain a colon, which makes them regex, so anchor them as ^my-plugin:db-agent$.
Example frontmatter hook, adapted from the docs:
--- name: db-migrator description: Runs database migrations tools: Read, Bash hooks: PreToolUse: - matcher: " Bash" hooks: - type: command command: "./scripts/security-check.sh" --- Enter fullscreen mode Exit fullscreen mode For a full map of every event and when it fires, see /claude-code-hooks-lifecycle-every-event-explained/.
In Claude Code, PreToolUse runs before a tool call and can block it, and PostToolUse runs after the tool succeeds. UserPromptSubmit runs when a prompt is submitted. Notification runs when Claude Code sends a notification and is for side effects only.
All four come from the hooks reference (as of October 2026). A few more details:
Exit codes work like this. 0 means success, and any JSON output is applied. 2 is a blocking error on events that can block. Any other code is a non-blocking error, and the action goes ahead unless you set onFailure: "block". For ready-made scripts on these events, see /claude-code-hooks-examples-7-copy-paste-scripts/.
A Claude Code hook is a JSON settings entry that runs a handler when an event matches. The handler is usually a shell command, and the entry links it to an event and a matcher. Here is how to set up a hook that stops a read-only subagent from writing files, even if its tool list was set up wrong.
If you'd rather not set this up by hand: the main official option is a Claude subscription that includes Claude Code, so you can ask Claude to write and check agent files for you. Claude Pro is $20/month ($17/month billed annually), and Max starts at $100/month with 5x or 20x Pro's usage (as of October 2026; check the official pricing page ). Pro suits solo builders running a few scheduled subagents. Max suits people who run many parallel or long jobs and keep hitting limits. Neither plan reviews your configuration for you, and the Free plan doesn't include Claude Code. If your runs are small and occasional, pay-as-you-go API use may cost less. Token tracking is covered in /claude-code-how-to-see-token-usage-and-reduce-it/.
If you don't set a tools field, it inherits the main conversation's built-in and MCP tools, minus a few that are always withheld, such as AskUserQuestion and EnterPlanMode. Background subagents, the default, keep only a fixed set of built-ins like Read, Edit, Write, Bash, Grep, Glob, WebFetch, and WebSearch, plus all MCP tools. Use tools or disallowedTools to narrow that set.
Hooks are handlers you configure in settings files or agent frontmatter, usually shell commands, that run automatically at lifecycle events such as PreToolUse, PostToolUse, SessionStart, and SubagentStop. They get JSON describing the event and can allow, deny, or change some actions. Hooks set in settings also run inside subagents.
All four are on the official hooks reference, with input fields, matcher values, and decision options for each. The sub-agents page covers how those events behave inside subagents and the SubagentStart/SubagentStop matchers.
Register a PreToolUse hook with a matcher such as Bash, and either exit with code 2 or print JSON with permissionDecision: "deny" and a reason. Add "onFailure": "block" if a crashing script should also block. For simple command patterns, a permissions.deny rule is often enough and needs no script.
This article was created with the help of AI. Sources are linked inline; check the official documentation for the latest details.