Claude Code Subagent allowed-tools: Syntax, Examples, Fixes
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."
Which option is right for you?
The best place to start depends on whether you want a safe default fast, a dependable daily setup, or the lowest token cost.
| Your situation | What to pick first | First page to open |
|---|---|---|
| Beginner, first custom subagent | tools: Read, Grep, Glob (read-only). Nothing can break | Create custom subagents |
| Daily work, subagent edits code and runs tests | tools: Read, Edit, Bash, Grep, Glob plus a permissions.deny rule for risky Bash commands | Configure permissions |
| Subagent needs one MCP server (GitHub, Slack) | tools: Read, mcp__github__*, or scope the server with mcpServers | Sub-agents: MCP section |
| You want everything except writes | disallowedTools: Write, Edit and keep inheriting | Sub-agents: restrict tools |
| Budget: keep context and tokens small | Inline MCP servers in the subagent so the parent never loads their tool descriptions | Sub-agents: scope MCP servers |
| Unattended or cron runs | Explicit tools list plus a PreToolUse hook as a second check | Hooks reference |
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.
What is the allowed-tools field in Claude Code subagents?
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.
| Where you write it | Key name | What it does |
|---|---|---|
Subagent file (.claude/agents/*.md) | tools | Allowlist: the subagent can use only these tools |
| Subagent file | disallowedTools | Denylist: removes tools from the inherited or listed pool |
Skill file (SKILL.md) | allowed-tools | Pre-approves listed tools for the turn that invokes the skill. Other tools stay callable |
| Agent SDK options | allowedTools / allowed_tools | Auto-approves tools before your permission callback runs |
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.
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.
How do you write the allowed-tools syntax (wildcards, lists, MCP tool names)?
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__*.
Steps to add or change a tool list
- Open (or create) the file:
.claude/agents/<name>.mdfor one project, or~/.claude/agents/<name>.mdfor all your projects. A subagent with the same name in the project folder wins over the one in your home folder. The priority table puts managed settings first, then the--agentsCLI flag, then project, then user, then plugins. - Add a
tools:line between the---markers. Spell tool names with their exact capitalization (Read, notread). - Save the file, then start a new session or ask Claude to use the subagent by name.
- On current versions, typing
/agentsonly reminds you to ask Claude or edit.claude/agents/and~/.claude/agents/directly. The interactive wizard with Running and Library tabs exists only on v2.1.197 and earlier (sub-agents docs).
Syntax patterns from the official docs
String form:
tools: Read, Grep, Glob, Bash
YAML list form (same meaning):
tools:
- Read
- Grep
- Glob
- Bash
Every tool from one MCP server, plus Read:
tools: Read, mcp__github__*
Everything inherited except one MCP server:
disallowedTools: mcp__github
No MCP tools at all, built-ins kept:
disallowedTools: mcp__*
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.
What the docs don't promise
- Command-level patterns in
tools. Skills acceptBash(git add ). For subagents, the docs only describe what a specifier does indisallowedTools: "An entry with a specifier, such asBash(git push ), still removes the whole tool." To keep Bash and block onlygit push, addBash(git push *)topermissions.denyin settings. That rule covers the main conversation and subagents. Agent(worker, researcher). This allowlist of spawnable subagent types works only for an agent running as the main thread throughclaude --agent. Inside an ordinary subagent definition, the docs say the type list in parentheses is ignored.TaskvsAgent. In v2.1.63 the subagent-spawning tool was renamed fromTasktoAgent, and olderTask(...)references still work as aliases (GitHub issue #29677). Hook scripts that checktool_name == "Task"stopped matching, so useAgentin anything new.
Defining the list from the CLI
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"
}
}'
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).
Inherit vs restrict: should a subagent inherit all tools or list them explicitly?
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.
Omit tools (inherit) | tools allowlist | disallowedTools denylist | |
|---|---|---|---|
| New MCP servers you add later | Picked up automatically | Not available until you add them | Picked up automatically |
| Risk of surprise side effects | Highest | Lowest | Medium |
| Maintenance | None | Update when the job changes | Update when a new risky tool appears |
| Good for | Ad-hoc helpers in an interactive session | Cron jobs, reviewers, anything unattended | "Everything but writes" style agents |
| Failure mode | Does too much | Missing tool, or launch refused if nothing resolves | Forgetting a new dangerous tool |
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:
- Permission mode can be overridden. When the main conversation runs in
bypassPermissions,acceptEdits, or auto mode, the subagent runs in that same mode and the docs say yourpermissionModesetting is ignored. The tool list is your real limit in those modes, so a tight list matters more than the permission mode you picked. To see how modes and rules combine for headless runs, read /claude-code-permissions-best-practices-headless-template/. - Built-in agents already restrict. The built-in Explore and Plan agents use "read-only tools; Write and Edit are denied," and general-purpose gets every tool available to subagents (sub-agents docs). If one of those already fits the job, you don't need a custom file.
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.
Which tools can be assigned to a Claude Code subagent?
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."
Tools a background subagent keeps
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):
| Group | Tool names |
|---|---|
| Read and search | Read, Grep, Glob, LSP |
| Change files | Edit, Write, NotebookEdit |
| Run commands | Bash, PowerShell, Monitor, TaskStop |
| Web | WebFetch, WebSearch |
| Workflow | TodoWrite, Skill, ToolSearch, SendMessage, Artifact |
| Worktrees | EnterWorktree, ExitWorktree |
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).
Tools no subagent gets, even if listed
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."
Starter lists by job (examples)
- Read-only auditor:
Read, Grep, Glob - Bug fixer that runs tests:
Read, Edit, Bash, Grep, Glob - Docs writer with citations:
Read, Write, WebFetch, WebSearch - Issue triager:
Read, mcp__github__*
What happens when a subagent's tools don't match its job (failure modes)?
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.
1. Every entry is wrong, so the subagent won't start
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.
2. Some entries are wrong, and nothing tells you
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.
3. Background runs lose a tool you listed
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/.
4. allowed-tools in a subagent file
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.
5. A Bash specifier that removes all of Bash
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.
6. Too many tools on a read-only job
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/.
7. A new MCP server reaches every inheriting agent
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.
What are Claude Code hooks and how do they relate to subagents?
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):
- Session-wide, from settings. Hooks in settings files, managed policy, and plugins all apply inside subagents, so "a
PreToolUsehook insettings.jsonalso runs before every tool a subagent uses." - Per subagent, from frontmatter. A
hooks:block in the agent file runs only while that subagent is active and is removed when it finishes. AStophook in frontmatter becomesSubagentStop(hooks reference).
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"
---
For a full map of every event and when it fires, see /claude-code-hooks-lifecycle-every-event-explained/.
What do Claude Code hooks PreToolUse, PostToolUse, UserPromptSubmit, and Notification do?
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.
| Event | When it fires | Matcher filters on | Can it block? | Typical subagent use | |
|---|---|---|---|---|---|
PreToolUse | Before a tool executes | Tool name (Bash, `Edit\ | Write, mcp__memory__.*`) | Yes: exit code 2, or permissionDecision: "deny" | Deny writes from a reviewer agent |
PostToolUse | After a tool succeeds | Tool name | Not the tool, which already ran. decision: "block" stops Claude from continuing | Run a linter after a subagent edits | |
UserPromptSubmit | When a prompt is submitted, before Claude processes it | No matcher; fires every time | Yes: exit code 2 or decision: "block" | Add project context to every turn | |
Notification | When Claude Code sends a notification | permission_prompt, idle_prompt, auth_success, agent_needs_input, agent_completed, and others | No, exit code is ignored | Alert you when an unattended run needs input |
All four come from the hooks reference (as of October 2026). A few more details:
- PreToolUse decisions:
permissionDecisionaccepts"allow","deny","ask", or"defer".updatedInputcan rewrite the tool's arguments before they run, andadditionalContextpasses a note to Claude. - PostToolUse:
updatedToolOutputcan replace what the tool returned. - UserPromptSubmit also fires on turns Claude Code starts by itself, which matters for automations.
- Default timeouts: 600 seconds for command, HTTP, and MCP tool hooks, 30 for prompt hooks, and 60 for agent hooks.
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/.
What is a Claude Code hook and how do you configure one?
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.
Steps
- Choose the scope.
.claude/settings.jsonis for one project and is shared through git..claude/settings.local.jsonis for one project and is gitignored.~/.claude/settings.jsoncovers all your projects (hooks reference). - Create the script at
.claude/hooks/deny-reviewer-writes.sh(example):
#!/bin/bash
INPUT=$(cat)
AGENT=$(echo "$INPUT" | jq -r '.agent_type // empty')
if [ "$AGENT" = "code-reviewer" ]; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "code-reviewer is read-only"
}
}'
else
exit 0
fi
- Make it executable:
chmod +x .claude/hooks/deny-reviewer-writes.sh. You also needjqinstalled. - Register it in
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/deny-reviewer-writes.sh"
}
]
}
]
}
}
- Open
/hooksin a session to confirm the hook is listed and to see which file it came from. - Run the reviewer on a small diff and ask it to fix something. The write attempt should come back denied, with your reason.
Matcher rules to get right
A matcher that is "", "", or left out matches everything. A value made only of letters, digits, _, -, spaces, , and | is treated as an exact name or a list. Anything else is treated as an unanchored JavaScript regex (hooks reference). So mcp__github__. is a regex that covers every GitHub MCP tool. You can narrow further with the if field, which takes permission-rule syntax such as "Bash(rm *)".
Common mistakes and fixes
- Script paths relative to
cwdbreak in worktrees. Use${CLAUDE_PROJECT_DIR}. In worktrees it stays at the main checkout, whilecwdpoints to the worktree. - Checking
tool_name == "Task". Since v2.1.63 the name isAgent(issue #29677). - Expecting Notification to block. It can't. Use PreToolUse or a permission rule.
- A crashing script lets the action through. A non-zero exit other than 2 doesn't block. For security hooks, set
"onFailure": "block". - Hooks seem to do nothing. Look for
"disableAllHooks": truein your settings, or a--settingsoverride in your cron command. Cron wrappers are covered in /claude-code-cron-job-setup-run-automations-overnight/.
Next steps
Do today (DIY):
- Search your agents for the wrong key:
grep -rn "allowed-tools" .claude/agents ~/.claude/agents. Rename every match totools. - For each subagent that runs unattended, write out a
toolslist built only from the background keep-list, then ask the subagent to list its tools before its next real run. - Add one PreToolUse hook like the one above for your most sensitive agent, and put
Bash(git push *)inpermissions.denyif no agent should push.
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/.
Related guides on this site:
- /claude-code-permissions-best-practices-headless-template/
- /claude-code-hooks-lifecycle-every-event-explained/
- /claude-code-hooks-examples-7-copy-paste-scripts/
- /ai-agent-guardrails-example-copy-paste-claude-code-rules/
- /claude-code-checkpoints-how-rollback-works-in-automations/
FAQ
What tools can a Claude Code subagent use?
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.
What are Claude Code hooks?
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.
Where are the docs for PreToolUse, PostToolUse, UserPromptSubmit, and Notification?
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.
How do I make a Claude Code hook block a command?
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.
Sources
- Create custom subagents – Claude Code Docs
- Hooks reference – Claude Code Docs
- Skills – Claude Code Docs
- Tools reference – Claude Code Docs
- Errors – Claude Code Docs
- Configure permissions – Claude Code Docs
- Configure permissions – Agent SDK
- Subagents in the SDK – Claude Code Docs
- Task→Agent tool rename in v2.1.63 – GitHub issue #29677
- Claude pricing
- Claude Code Subagents: A 2026 Practical Guide – Tembo
Spotted something out of date or wrong? Tell us at shigoto.ai.note@gmail.com. Guides are re-checked against official sources on a rolling schedule.