Custom subagents in Claude Code: scoped specialists you can verify
A custom subagent in Claude Code is a Markdown file in .claude/agents/ (project) or ~/.claude/agents/ (personal). Its YAML frontmatter sets the name, the delegation description, tools, model and permissions, and its body becomes the system prompt. Each subagent runs in its own context window and returns only a summary, so noisy side work stays out of the main conversation.
Forty minutes into a refactor, your session holds three full test runs, two repository-wide grep sweeps and a migration log. The constraint you set at the start (“do not touch the public API”) is buried under all of it, and the review you now want would compete for the same context. This page is for developers who want that side work done by scoped specialists, and for tech leads who commit a shared set of subagents to the repository and need them to behave the same for eight people.
What a working subagent setup gives you
Section titled “What a working subagent setup gives you”- Three subagent files you can commit today: a read-only reviewer that returns a verdict, a cheap test runner that reports only failures, and a worker that edits in its own git worktree.
- A 30-line CI check that catches the frontmatter mistakes Claude Code skips without an error.
- A rule for choosing each subagent’s model and effort, and a way to confirm what actually ran.
- A verification routine that treats a subagent’s report as a claim to check, not as evidence.
- A symptom-to-recovery table for the failures that come up in practice.
When should you use a subagent instead of a skill, a fork or a background session?
Section titled “When should you use a subagent instead of a skill, a fork or a background session?”A subagent starts with a fresh context and sends back one summary. That suits self-contained work that produces output you will not read again, and does not suit work that needs your conversation history or constant back-and-forth.
| You need… | Use | Why |
|---|---|---|
| A noisy, self-contained side task: a test run, a log triage, a codebase search | A custom subagent | Own context window, own tool limits, only the summary comes back |
| A reusable procedure that should run with everything you have said so far | A skill | Skills run in the main conversation’s context |
| A side task that needs the whole conversation, without re-explaining it | A fork: /subtask <task> (v2.1.212+) | A fork inherits the conversation and reuses the parent’s prompt cache |
| Several independent tasks you supervise, each in its own session | Agent view (claude agents, claude --bg) | Separate sessions, separate worktrees, one dashboard |
| Dozens of agents driven by a script you can rerun | Dynamic workflows | Orchestration lives in code, not in one conversation |
| A quick question about something already in the conversation | /btw | Sees your context, has no tools, and the answer is not added to history |
Claude Code also ships built-in subagents: Explore and Plan (read-only research) and general-purpose (can edit). A custom subagent earns its place when you keep spawning the same kind of worker with the same instructions and the same limits.
How is a subagent file built?
Section titled “How is a subagent file built?”A subagent is one Markdown file. Only name and description are required:
---name: code-reviewerdescription: Reviews the current branch's changes for correctness and security. Use proactively before a commit or pull request.tools: Read, Grep, Glob, Bash---
You review code. You never edit files. Report findings with file and line.The body replaces the Claude Code system prompt for that subagent: it receives your text plus basic environment details such as the working directory. The name is the identity; the file name and any subfolder under agents/ do not matter.
Where subagent files live, and which one wins
Section titled “Where subagent files live, and which one wins”When two definitions share a name, Claude Code uses the one from the higher-priority location:
| Priority | Location | Scope |
|---|---|---|
| 1 (highest) | Managed settings | Organization-wide, set by admins |
| 2 | --agents '{…}' JSON flag | The current session only |
| 3 | .claude/agents/ | This project; commit it |
| 4 | ~/.claude/agents/ | All your projects |
| 5 (lowest) | A plugin’s agents/ directory | Wherever the plugin is enabled |
Claude Code watches the project and user agents directories, so an edit takes effect at the next delegation without a restart. The exception: if the directory did not exist when the session started, restart once after creating the first file.
What does a subagent see when it starts?
Section titled “What does a subagent see when it starts?”A non-fork subagent starts with:
- Its own system prompt (the file body) plus environment details.
- The task message Claude writes when it delegates. This is the only channel for anything specific to the current job.
- Your
CLAUDE.mdfiles, every level the main conversation loads, including anAGENTS.mdloaded as project instructions. SetomitClaudeMd: true(v2.1.271+) to skip the user, project and localCLAUDE.mdfiles (managed policy files still load) for a subagent that needs nothing but the task. - A git status snapshot and the full content of any skill listed in its
skillsfield.
It does not see your conversation history, the files Claude already read, the main session’s auto memory, or your output style. So a rule you stated in chat ten minutes ago does not reach the subagent unless it is in CLAUDE.md, in the subagent’s own prompt, or in the delegation message. For where each CLAUDE.md level lives and what belongs in it, see the memory system.
Build three subagents that end in evidence
Section titled “Build three subagents that end in evidence”The three files below split the work: one judges, one runs checks, one edits in isolation. Each prompt ends in a fixed report format, which is what lets you check the output without reading the transcript.
-
Create the reviewer. It can read and run commands, but it cannot write or edit files, so it cannot “fix” a test to make its own verdict true.
---name: code-reviewerdescription: Reviews the diff against main for correctness, security and missing tests. Use proactively before a commit or pull request.tools: Read, Grep, Glob, BashdisallowedTools: Write, Edit, NotebookEditmodel: inherit---You review the changes between the merge base with main and the working tree.You never modify files.1. Run `git diff --merge-base main --stat`, then read every changed file in full.2. Check behaviour, not style: wrong logic, unhandled errors, injection,authorization gaps, and changed behaviour without a test that covers it.3. For every finding, run or name the command or test that demonstrates it.Reply in exactly this format and nothing else:VERDICT: APPROVE | REQUEST_CHANGESFINDINGS:- [blocker|major|minor] path/to/file.ts:LINE: what is wrong. Evidence: <command or test>CHECKED: <each command you ran, with its exit code>Skip anything a linter or formatter would catch. If you found nothing,say so and list what you checked.Bashcan still change files through shell commands. For a strictly read-only reviewer, dropBashand let the delegation message carry the diff, or addpermissions.denyrules for the commands you fear. -
Create the test runner. Test output is the classic context flood. This subagent runs on the
haikualias because the job is mechanical and high volume.---name: test-runnerdescription: Runs tests and reports only the failures. Use whenever a test command would print more than a screen of output.tools: Bash, Read, Grep, Globmodel: haikumaxTurns: 15---Run the test command given in the task. If none is given, use the project'sdefault test script from package.json, pyproject.toml or the Makefile.Never edit a file.Reply in exactly this format:COMMAND: <the exact command>EXIT: <exit code>RESULT: <passed> passed, <failed> failed, <skipped> skippedFAILURES:- <test name> at path:LINE: <assertion message, first 3 lines>Before you call a failure flaky, re-run that one test once and report both runs. -
Create an isolated worker for fan-out.
isolation: worktreegives every run its own temporary git worktree, so parallel workers cannot overwrite each other’s edits.---name: migration-workerdescription: Applies one mechanical change (a rename, an API migration) to the files named in the task. Use for parallel fan-out across modules.tools: Read, Edit, Write, Grep, Glob, Bashisolation: worktreemodel: sonneteffort: medium---Make only the change the task describes, only in the files it names.Then run the type check and the tests that cover the changed files.Commit on your worktree branch with a message that names the change.Reply with: BRANCH, FILES CHANGED, and every command you ran with its exit code.If a check fails and the fix is outside your files, stop and report it.A subagent worktree branches from your repository’s default branch, not from your current
HEAD. When workers must build on unpushed commits of your feature branch, set this in.claude/settings.json:{"worktree": { "baseRef": "head" }} -
Add a lint check for the files. Claude Code skips a subagent file without an error in the session when the frontmatter does not parse, when
---is not on line 1, or whennameordescriptionis missing. It also ignores unknown fields silently, sodisallowed_toolsinstead ofdisallowedToolsremoves nothing. Save this asscripts/lint_agents.py(it needspyyaml):#!/usr/bin/env python3"""Fail CI when a subagent file would be skipped or would silently ignore a field."""import pathlib, sys, yamlKNOWN = {"name", "description", "tools", "disallowedTools", "model", "permissionMode","maxTurns", "skills", "mcpServers", "hooks", "memory", "background","omitClaudeMd", "effort", "isolation", "color", "initialPrompt", "experimental"}errors = []for path in sorted(pathlib.Path(".claude/agents").rglob("*.md")):text = path.read_text(encoding="utf-8").replace("\r\n", "\n")if not text.startswith("---\n"):print(f"warning: {path}: no frontmatter, so Claude Code treats it as a doc")continuetry:meta = yaml.safe_load(text.split("---\n", 2)[1]) or {}except yaml.YAMLError as exc:errors.append(f"{path}: YAML does not parse: {exc}")continueif not isinstance(meta, dict):errors.append(f"{path}: frontmatter must be a YAML mapping")continuename = str(meta.get("name", ""))if not name or not meta.get("description"):errors.append(f"{path}: needs both name and description")if name.startswith("-") or ":" in name:errors.append(f"{path}: name must not start with '-' or contain ':'")for key in sorted(set(meta) - KNOWN):errors.append(f"{path}: unknown field '{key}' (ignored at runtime)")print("\n".join(errors) or "agents OK")sys.exit(1 if errors else 0)Run it in the same CI job as your linter. The job needs no secrets and only read access:
.github/workflows/agent-config.yml name: agent-configon: [pull_request]permissions:contents: readjobs:lint-agents:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v7with:persist-credentials: false- run: pip install pyyaml && python3 scripts/lint_agents.pyOn
pull_request, GitHub runs this workflow andscripts/lint_agents.pyfrom the pull request itself, so a pull request can edit the check in the same diff as the agent file. Put.github/andscripts/lint_agents.pyunderCODEOWNERStogether with.claude/agents/and require code-owner review in branch protection.The vendor docs suggest
claude plugin validate .claude/agentsfor the same job. On v2.1.283 it printed “Validation passed” for a directory holding a file with broken YAML, a file with a misspelled field and a file with noname, even with--strict(tested 2026-09-26), so do not rely on it for project agent files yet. -
Try it. Commit the three files and the script, then paste the first prompt from the copy-paste section below. In the transcript, a delegation shows as a row such as
code-reviewer(Review the diff). Run/taskswhile a subagent is running to see its row and the model it runs on.
How do you make Claude pick the right subagent?
Section titled “How do you make Claude pick the right subagent?”Claude chooses a subagent from its description, so write the description as a routing rule: what the subagent does, and the situation that should trigger it. “Use proactively after code changes” encourages automatic delegation. Keep descriptions short: when the combined descriptions of your custom subagents pass 15,000 tokens, Claude Code warns at startup, and every token of them sits in the main context.
When routing matters, stop relying on it. There are three levels of control:
| How | What it guarantees |
|---|---|
| Name it in the prompt: “Use the test-runner subagent…” | Nothing: Claude usually delegates, but decides |
@-mention: type @ and pick it, or write @agent-test-runner | That subagent runs for this task; Claude still writes its task message |
Session-wide: claude --agent code-reviewer, or "agent": "code-reviewer" in .claude/settings.json | The main thread itself takes the subagent’s prompt, tools and model |
--agent combines with background sessions: claude --agent code-reviewer --bg "review the open changes on this branch" starts the reviewer as its own session, which you then manage in agent view. In the agent-view dispatch box, a prompt whose first word matches a subagent name dispatches that subagent, so a prompt that starts with “test-runner” is not plain text.
To stop Claude from using a subagent at all, deny it in settings with its exact name:
{ "permissions": { "deny": ["Agent(Explore)", "Agent(migration-worker)"] }}Control what each subagent can touch
Section titled “Control what each subagent can touch”The tools field is an allowlist and disallowedTools is a denylist; when both are set, the denylist applies first. Four rules decide whether a restriction holds:
- A specifier removes the whole tool.
disallowedTools: Bash(git push *)takes away all ofBash, not onlygit push. To keep Bash and block one command, addBash(git push *)topermissions.denyin settings; that rule covers the main conversation and every subagent. permissionModeis often ignored. When the main conversation runs in auto mode,acceptEditsorbypassPermissions, the subagent runs in that same mode whatever its file says. Auto mode is the starting mode for interactive sessions from v2.1.283 (thelatestchannel), unless a settings file sets another default mode, so in practice thetoolslist and deny rules are your real limits. A subagent that declaresbypassPermissionsnever gets more than the main conversation has.- Background subagents get fewer tools. In an interactive session, fork mode is on by default and Claude Code runs the subagents Claude spawns in the background. A background subagent keeps every MCP tool but only a fixed set of built-in tools (such as
Read,Grep,Glob,Bash,Edit,Write,WebFetchandSkill). The same file can therefore work in the foreground and miss a tool in the background. Its permission prompts appear in your main session, and a “for the rest of the session” answer there applies to the whole session. - Plugin subagents drop three fields. A subagent that comes from a plugin ignores
hooks,mcpServersandpermissionMode. Copy the file into.claude/agents/if you need them.
Two fields add capability without adding context to the main session. mcpServers with an inline server definition connects that server only while the subagent runs, so its tool descriptions never load into the main conversation. hooks in frontmatter run only while that subagent is active, for example a PreToolUse hook that blocks SQL writes for a database reader. Both load from a project .claude/agents/ file only after you accept the workspace trust dialog for that folder; a claude -p run does not count as trusted.
Choose the model and effort for each subagent
Section titled “Choose the model and effort for each subagent”Claude Code resolves a subagent’s model in this order: a model Claude passes for that one call, then the file’s model field, then the CLAUDE_CODE_SUBAGENT_MODEL environment variable, then the main conversation’s model. The field takes inherit, an alias (opus, sonnet, haiku, fable) or a full model ID.
Follow the same rule as the rest of this site: start on the default model, tune effort before switching model, and switch only when your own results say so.
- Leave judgement work (review, design critique, debugging) on
inherit. On thelatestchannel that is Claude Opus 5.5 by default; onstable(v2.1.274) the default depends on your plan. - Move mechanical, high-volume work (test runs, log triage, bulk renames) to
sonnetorhaiku, and seteffortper subagent before you change the model. - Reserve
fablefor the longest-horizon work, chosen deliberately.
Prices and context windows for each model are on the model comparison hub.
Two surprises to know about. First, an alias from the same family as the main conversation’s model resolves to the main model: with the session on Opus 5.5, model: opus runs Opus 5.5, not whatever the alias points to elsewhere. Second, CLAUDE_CODE_SUBAGENT_MODEL is only a default; to force one model onto every subagent, also set CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1 (v2.1.257+). Confirm the result in /tasks, which shows the model on each running subagent’s row.
Every subagent sends its own requests against the same usage limits as your main conversation, and every returned summary lands in your main context; the fixed, short report formats above keep the second cost down. See cost control for Claude Code for budgets across a team.
How do you verify what a subagent reports?
Section titled “How do you verify what a subagent reports?”A subagent’s report is a claim written by a model that you did not watch. Treat it the way you treat a colleague’s “tests pass” in chat: useful, and checked before it counts.
- Demand evidence in the format. Every finding must carry a
path:lineand the command or test that shows it. A finding without evidence is a question, not a blocker. - Re-run the decisive check yourself. When the test runner says “0 failed”, have the main session run the same command once and compare exit codes. It is one command, not a full re-read.
- Separate the writer from the judge. The session that wrote the code should not grade it. The reviewer above has no
WriteorEdit, so it cannot edit a test to make its own verdict true. For tests the implementing agent must not weaken, see protecting the oracle. - Keep the gate outside the agent. CI runs the type check, lint and tests on the pull request. A subagent’s
APPROVEinforms the merge decision; it never replaces CI. The developer who opens the pull request signs off on the merge, and the tech lead owns.claude/agents/through CODEOWNERS. - Read the scan marker. From v2.1.210, Claude Code scans each subagent report and prepends a marker line when the text imitates Claude Code’s own tags or mentions permission settings such as
bypassPermissions. A review that asks you to loosen permissions is a finding about the input it read, not advice. - Keep the audit trail. Subagent transcripts are stored as
~/.claude/projects/<project>/<session-id>/subagents/agent-<agent-id>.jsonland deleted aftercleanupPeriodDays(30 days by default). ASubagentStophook insettings.jsoncan copy the final report somewhere durable.
For the method behind this (explicit inputs, a bounded tool set, a fixed output and a stop condition), see scoped subagents: delegate bounded work, not responsibility.
Copy-paste prompts for custom subagents
Section titled “Copy-paste prompts for custom subagents”Share subagents across a team
Section titled “Share subagents across a team”- One repository: commit
.claude/agents/and add a CODEOWNERS line for it, so a change to a shared reviewer’s rules gets the same review as a change to CI. - Personal helpers: keep them in
~/.claude/agents/. A project file with the samenameoverrides a personal one. - Many repositories: package the subagents as a plugin and install it from a team marketplace; see Claude Code plugins. Plugin subagents appear in the @-mention list under a scoped name such as
my-plugin:code-reviewer.claude plugin evalmeasures how reliably Claude delegates to a plugin’s subagents across a set of prompts. - Organization-wide: admins deploy files to
.claude/agents/in the managed settings directory; those override project and user definitions with the same name. - Coming from another tool:
/import codex,/import cursoror/import geminibrings in that tool’s subagents along with its instruction files, commands and MCP servers.
Codex and Cursor have their own subagent features. For the Codex side, see orchestrating multiple Codex agents; the scoped subagents guide compares all three tools.
When custom subagents break
Section titled “When custom subagents break”| Symptom | Likely cause | Recovery |
|---|---|---|
| The subagent is never chosen | The description is vague, overlaps another one, or is lost in a large description budget | Rewrite it as a routing rule; @-mention the subagent when it must run |
| The subagent does not exist in the session | Frontmatter does not parse, --- is not on line 1, name or description is missing, or name contains : | Run claude --debug and read the log; add the lint script to CI |
| A field has no effect | The key is misspelled or in the wrong case (disallowed_tools, permission_mode) | Use the camelCase names; the lint script flags unknown keys |
| “Agent would be spawned with zero tools” | Every entry in tools is misspelled or unavailable to subagents | Fix the names; check against the tools the main session actually has |
| It works in the foreground but a tool is missing in the background | Background subagents keep only a fixed set of built-in tools | Use tools from that set, or set CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 for that run |
permissionMode: plan did not stop an edit | The main session was in auto mode, acceptEdits or bypassPermissions, which override the field | Remove Write and Edit from tools; add deny rules in settings |
| A worktree worker cannot see your latest commits | Subagent worktrees branch from the default branch | Set worktree.baseRef to "head", or push the base branch first |
| Frontmatter hooks or inline MCP servers never run | The folder holding the agent file is not trusted; -p sessions do not count | Open an interactive session in that folder and accept the trust dialog |
| A new subagent is “not found” | The agents directory was created after the session started | Restart Claude Code once |
| The subagent runs a different model than the file says | Same-family alias resolution, CLAUDE_CODE_SUBAGENT_MODEL_FORCE, or an availableModels allowlist substitution | Check the model in /tasks; use a full model ID if you need an exact model |
| “Concurrent subagent limit reached” | 20 subagents are already running in the session (the default) | Let some finish, batch the work, or raise CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS |
| The main context fills up anyway | Each subagent returns a long free-form report | Put a fixed, short reply format at the end of every subagent prompt |