cd /news/ai-agents/claude-code-subagent-allowed-tools-s… · home › topics › ai-agents › article
[ARTICLE · art-162057] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Claude Code Subagent allowed-tools: Syntax, Examples, Fixes

A developer documented that Claude Code subagents do not support an `allowed-tools` frontmatter field; the correct key is `tools`, a comma-separated allowlist (e.g. `tools: Read, Grep, Glob`), with `disallowedTools` serving as the matching denylist. The writeup explains that `allowed-tools` belongs to Skills, where it pre-approves tools for a single turn without restricting anything, and warns that placing it in a subagent file causes the subagent to inherit every available tool. It also recommends pairing explicit tool lists with `permissions.deny` rules or `PreToolUse` hooks for unattended runs.

by read15 min views1 publishedNov 1, 2026

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. 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.

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 apermissions.deny rule for risky Bash commands Configure permissions
Subagent needs one MCP server (GitHub, Slack) tools: Read, mcp__github__* , or scope the server withmcpServers 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 aPreToolUse 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.

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.

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__*.

.claude/agents/<name>.md for one project, or ~/.claude/agents/<name>.md for all your projects. A subagent with the same name in the project folder wins over the one in your home folder. The --agents CLI flag, then project, then user, then plugins.tools: line between the --- markers. Spell tool names with their exact capitalization (Read, not read)./agents only reminds you to ask Claude or edit .claude/agents/ and ~/.claude/agents/ directly. The interactive wizard with 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.

tools. Bash(git add *). For subagents, the docs only describe what a specifier does in disallowedTools: " Bash(git push *), still removes the whole tool git push, add permissions.deny in settings. That rule covers the main conversation and subagents.Agent(worker, researcher). claude --agent. Inside an ordinary subagent definition, the docs say the type list in parentheses is ignored.Task vs Agent. Task(...) references still work as aliases (tool_name == "Task" stopped matching, so use 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).

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:

bypassPermissions, acceptEdits, or auto mode, the subagent runs in that same mode and the docs say your permissionMode setting 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/. 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):

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).

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."

Read, Grep, Glob Read, Edit, Bash, Grep, Glob Read, Write, WebFetch, WebSearch Read, mcp__github__* 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/.

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.

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):

PreToolUse hook in settings.json also runs before every tool a subagent uses.hooks: block in the agent file runs only while that subagent is active and is removed when it finishes. A Stop hook in frontmatter becomes SubagentStop ( 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/.

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"
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:

permissionDecision accepts "allow", "deny", "ask", or "defer". updatedInput can rewrite the tool's arguments before they run, and additionalContext passes a note to Claude.updatedToolOutput can replace what the tool returned. 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.

.claude/settings.json is for one project and is shared through git. .claude/settings.local.json is for one project and is gitignored. ~/.claude/settings.json covers all your projects (.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

chmod +x .claude/hooks/deny-reviewer-writes.sh. You also need jq installed.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|NotebookEdit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/deny-reviewer-writes.sh"
          }
        ]
      }
    ]
  }
}

/hooks in a session to confirm the hook is listed and to see which file it came from. 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 *)".

cwd break in worktrees.${CLAUDE_PROJECT_DIR}. In worktrees it stays at the main checkout, while "onFailure": "block"."disableAllHooks": true in your settings, or a --settings override in your cron command. Cron wrappers are covered in /claude-code-cron-job-setup-run-automations-overnight/. Do today (DIY):

grep -rn "allowed-tools" .claude/agents ~/.claude/agents. Rename every match to 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:

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.

── more in #ai-agents 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-code-subagent…] indexed:0 read:15min 2026-11-01 · —