`tools:` allowlist is enforced for subagents but silently dropped for named agents (teammates)
Summary
A subagent definition's declared tool restriction is enforced on one dispatch path and silently ignored on the other. The discriminator is the name parameter.
The clearest evidence is a matched pair — same session, same lead, same permission mode, same agent type, same minute. Only name moved:
| Call | Result |
|---|---|
| Agent({subagent_type: "Explore"}) | Error: No such tool available: Write. Write exists but is not enabled in this context. |
| Agent({name: "probe-tools-bound", subagent_type: "Explore"}) | Write succeeded. Edit succeeded. |
Explore is a shipped agent type. Its registry entry declares:
All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit
The named call executed two of the six tools that declaration excludes. No error, no warning — the agent spawned normally, reported normally, and held tools its type says it does not have.
Results were verified on disk, not taken from the agent's self-report: the target file exists containing the content written by the second call.
This is not bypassPermissions
That was our leading hypothesis and the matched pair rules it out. Both arms ran under the same inherited bypassPermissions lead, in the same session, seconds apart. The unnamed arm was bounded correctly. If bypass defeated tool declarations, it would have defeated both.
Not specific to built-in types
Reproduced with a user-scope custom definition:
---
name: probe-custom-bound
tools: Read, Grep, Glob
model: haiku
---
Spawned as Agent({name: "probe-custom-named", subagent_type: "probe-custom-bound"}), it wrote a file with the Write tool. Verified on disk. The team registry recorded agentType='probe-custom-bound', confirming the definition was resolved and applied — the tools field specifically was dropped.
Independent replication
Reproduced from a second lead in a different git worktree, same build, using the shipped Explore type rather than an authored definition. Same outcome. This rules out a single anomalous session and rules out an authoring error in the custom definition.
Named dispatch appears to be teammate dispatch
Named Agent() spawns behave as agent-team teammates rather than subagents:
- enrolled in
~/.claude/teams/session-<id>/config.jsonwithagentId: "<name>@session-<id>" - delivered via mailbox (
inboxes/is created lazily on first send) - their output returns as a teammate message, not a task result
- they emit
idle_notificationon completion
Five unnamed spawns in the same session produced zero registry entries and returned ordinary task results. The Agent tool's team_name parameter is documented as "Deprecated; ignored. The session has a single implicit team," which is consistent with team spawning having been folded into Agent.
Documentation conflict
The agent-teams documentation states:
The teammate honors that definition'stoolsallowlist andmodel.
The tools half does not hold. (We did not test the model half.)
Why this matters
tools: is the mechanism the subagent documentation offers for "Enforce constraints by limiting which tools a subagent can use." On the named path it is a containment control that reads as applied and is not. The failure is silent in both directions — no spawn-time warning that the field was dropped, and no runtime error when the excluded tool is called.
A reviewer role declared read-only can write. An audit role restricted to Read, Grep, Glob can edit. Nothing surfaces the gap.
Reproduction
- In a session with
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, spawnAgent({subagent_type: "Explore"})with noname, instructing it to create a file with theWritetool. It is refused. - Spawn
Agent({name: "anything", subagent_type: "Explore"})with the same instruction. It succeeds. - Confirm the file exists on disk.
Environment
- Claude Code 2.1.220
- macOS (darwin 25.5.0)
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1(user scope)teammateModeunset (defaults toin-process)- Lead running in
bypassPermissions; the bounded unnamed arm ran under the same mode
The mechanism works — it is specifically the named path that loses it
A follow-up check confirms tools: is not broken generally. A custom definition already present at session start resolves for unnamed subagent dispatch, and its tools: allowlist is enforced there. So role definitions do provide real tool containment; a named spawn is the only place it disappears.
That narrows this to a single dispatch path rather than a general failure of the field, which is why it reads as a defect rather than a design choice.
One incidental note in case it is the same root cause: a definition authored mid-session did not resolve for unnamed dispatch, and the error listed exactly the agent roster from the session-start system prompt — suggesting the subagent registry is snapshotted at startup while the named path resolves definitions immediately. That is a separate behavior from the one reported here, and it does not affect the report, which uses the shipped Explore type on both arms.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗