A skill's unresolvable `agent:` silently falls back to `general-purpose`, widening its toolset
Version: Claude Code 2.1.220
Platform: macOS (Darwin 25.4.0)
Summary
When a plugin skill declares context: fork with agent: <name>, and <name>
does not resolve, Claude Code forks a general-purpose agent instead. There
is no error, no warning, and nothing in the transcript indicating a substitution
took place.
This matters beyond a typo class of bug, because the substituted agent has a
wider toolset than the one specified. A subagent defined with a restrictedtools: list — the only real toolset restriction available, since allowed-tools
on a skill does not confine anything — is replaced by one with full access.
In our case the named agent was a read-only auditor:
# agents/auditor.md
name: auditor
tools: Read, Grep, Glob, Bash, WebFetch # deliberately no Edit, no Write
The whole point of that agent is that it cannot modify what it audits. The skill
that invoked it ran with Edit and Write available for months of authoring
without anyone noticing, because the output looked correct.
Reproduction
- In a plugin, define an agent at
agents/auditor.mdwith a restrictedtools:
list.
- In a skill in the same plugin, set frontmatter:
``yaml``
context: fork
agent: auditor
- Invoke the skill:
claude -p '/my-plugin:my-skill' - Read the recorded agent type:
``sh``
cat ~/.claude/projects/<project>/<sessionId>/subagents/agent-*.meta.json
Observed: {"agentType":"general-purpose"}
Then change the frontmatter to the plugin-scoped form:
agent: my-plugin:auditor
and repeat.
Observed: {"agentType":"my-plugin:auditor"}
Two consecutive runs in the same project directory, nothing else changed:
17:07:44 {"agentType":"general-purpose"} agent-a1c9555b3a6a0b626.meta.json
17:10:21 {"agentType":"project-kit:auditor"} agent-a0f66cd7fa31341dd.meta.json
Expected
One of:
- Resolve the bare name within the declaring plugin's own agents, the way a
reader would expect a plugin-local reference to work; or
- Fail loudly — refuse to launch, or emit a visible warning naming the
unresolved agent and the fallback being used.
Silently substituting an agent with broader tool access is the outcome that
should not be available. The failure is invisible at every layer a user would
check: the skill runs, the output is plausible, and the only trace is ameta.json most people do not know exists.
Notes on scope of this report
- Verified only for a plugin skill referencing a plugin agent. Whether a
project-level or user-level agent behaves the same is untested here.
sub-agents.mddocuments that hook matchers for plugin subagents use the
plugin-scoped identifier (my-plugin:db-agent), so scoping is clearly the
intended reference form. The defect is the silent fallback, not the
requirement.
- Not tested against any version other than 2.1.220.
- The substituted agent in our runs happened to use only
Bash, so nothing was
damaged. That is luck, not a mitigation.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Ran into this problem, too. This is a serious issue — first, it leads to unwanted model escalation or degradation, second, it's a session limit eater, providing the lower-intelligence agents are silently replaced with a
general-purposeagent that inherits e.g. the Fable model.Adding field evidence from a long multi-agent session that reproduces #83133 in production and quantifies a consequence I think the current title under-states: **when a
context: forkskill'sagent:fails to resolve, the fallbackgeneral-purposeagent inherits the main session model, not the target agent's pinned model — silently, with no warning.** For a skill whose whole purpose is to run cheap work on a small model, this is a correctness-and-cost defect, not only a toolset-widening one.Setup
A local plugin (
pipr) ships a fork skill:Plugin agents register under a namespaced id (
pipr:pipr-runner), so the bareagent: pipr-runnerdoes not resolve — exactly the case in this issue. Sibling plugins in the same session used the namespaced form (agent: infra-ops:jira-operator, etc.).What was observed (single session, 2026-08-12/13, CC 2.1.228)
Across one session I counted every fork marker against the model each spawned agent actually ran (from the per-agent transcripts). During a ~5-hour window, only the bare-ref skill fell back; every namespaced sibling resolved correctly the entire time:
| Fork skill | Declared agent | Resolved to | Model that ran | Result |
|---|---|---|---|---|
|
pipr:pipr(bareagent: pipr-runner) | pipr:pipr-runner (model: haiku) | general-purpose | claude-fable-5 ×2, claude-opus-5 ×17 | ❌ 19 fallbacks ||
infra-ops:jira-ops(namespaced) | jira-operator (model: sonnet) | infra-ops:jira-operator | claude-sonnet-5 | ✅ 10/10 ||
infra-ops:plastic-ops(namespaced) | plastic-operator (model: sonnet) | infra-ops:plastic-operator | claude-sonnet-5 | ✅ ||
epic-worker:group-bugfix(namespaced) | bug-fixer | epic-worker:bug-fixer | (session model) | ✅ resolved |The bare-ref failures and the namespaced successes were interleaved in the same minutes (e.g. 16:11
jira-ops→sonnet ✅ andpipr→general-purpose ❌ back to back), which rules out a transient/environment cause. After the plugin was updated mid-session to the namespaced ref (agent: pipr:pipr-runner), the same fork resolved correctly on every subsequent call and ran Haiku (8/8).The consequence worth highlighting
The fallback did two things silently:
claude-opus-5/claude-fable-5, the main session's model, instead of the intendedhaiku. A skill explicitly pinned to Haiku for cost ran ~19 CLI-wrapper operations on the frontier model, for ~5 hours, with no warning anywhere in the UI or logs. The only way we found it was post-hoc forensics on the per-agent transcripts.So the silent fallback is not just a safety issue (tools); it quietly inverts the operator's cost decision. The pin is the whole reason the agent exists, and it is dropped without a trace.
Ask
The primary fix (error/warn on an unresolvable
agent:) is already the subject of this issue — this comment is just to reinforce it with real numbers. If a hard error is too aggressive for back-compat, even a single stderr/telemetry line at fork time —— would have turned a 5-hour invisible regression into a one-line diagnosis. The model-inheritance half of the fallback is the part that currently has zero observability.
Repro (minimal)
context: forkand a bareagent: <name>whose agent registers namespaced as<plugin>:<name>, withmodel: haikuon the agent.agentTypeisgeneral-purposeand the dispatched model equals the session model, nothaiku. No warning is emitted.agent: <plugin>:<name>) → resolves correctly and honors the Haiku pin._Note: distinct from #86058 (frontmatter
model: opusrunning the parent model when the newest Opus family is gated). That is a model-stepping bug on a resolved agent; this is a resolution failure that happens to also drop the pin._@bcherny repro here, re threads convo.