tools: frontmatter — misleading error message + silent acceptance of non-documented Agent(a), Agent(b) syntax

Resolved 💬 1 comment Opened Apr 23, 2026 by benzema69 Closed May 27, 2026

Upstream issue draft — Claude Code 2.1.118: tools: frontmatter — misleading error message + silent acceptance of non-documented Agent(a), Agent(b) syntax

Draft for submission at https://github.com/anthropics/claude-code/issues*

---

Summary

Two related usability issues in the tools: frontmatter parsing of subagent definition files (.claude/agents/*.md). Neither is a parser bug per se — the parser behaves logically — but they compound into a confusing 4-hour debugging session when a user writes a non-documented variant of the Agent(...) syntax:

  1. Error message is misleading. When a subagent invocation is blocked because the invoking agent's tools: whitelist does not include it, the error reads: Agent type 'X' has been denied by permission rule 'Agent(X)' from settings. This points the user toward settings.json permission rules, when the actual restriction lives in the calling agent's tools: frontmatter (a separate, internal validator).
  1. Parser silently degrades non-documented syntax. The official documentation (https://code.claude.com/docs/en/sub-agents section on tools:) shows canonical whitelist syntax as a single Agent(a, b, c) entry. However, multiple-entry notation Agent(a), Agent(b), Agent(c) is silently accepted and interpreted as "only the last Agent(X) wins" (each declaration overrides the previous whitelist set). There is no warning, no hint, no error — the user sees behavior consistent with canonical syntax for one subagent and denial for the others.

Together, these two issues created a diagnostic dead-end that consumed ~4 hours of a single developer's time before an official-doc re-read disambiguated.

Version affected: Claude Code 2.1.118 (likely the entire 2.1.x line — not bisected).

---

Reproducer

Environment: Ubuntu 24.04 WSL 2, Claude Code 2.1.118 via native installer, defaultMode: bypassPermissions.

mkdir -p /tmp/cc-syntax-repro/.claude/agents && cd /tmp/cc-syntax-repro

cat > .claude/settings.json <<'JSON'
{"agent":"orchestrator","permissions":{"defaultMode":"bypassPermissions","allow":[]}}
JSON

# Variant A: non-documented multi-declaration syntax
cat > .claude/agents/orchestrator.md <<'MD'
---
name: orchestrator
description: Delegate probes to alice and bob.
tools: Read, Agent(alice), Agent(bob)
model: sonnet
---
Invoke Agent(subagent_type="alice") with prompt "ACK probe", then invoke Agent(subagent_type="bob") with prompt "ACK probe". Report each return verbatim.
MD

cat > .claude/agents/alice.md <<'MD'
---
name: alice
description: probe
tools: Read
model: sonnet
---
Respond with the literal line "ACK alice" and stop.
MD

cat > .claude/agents/bob.md <<'MD'
---
name: bob
description: probe
tools: Read
model: sonnet
---
Respond with the literal line "ACK bob" and stop.
MD

claude "invoke alice and bob and report each result verbatim"

Observed:

  • bob (last Agent(X) in tools) returns ACK bob.
  • alice invocation returns: Agent type 'alice' has been denied by permission rule 'Agent(alice)' from settings.

Expected from the user's perspective (having written both Agent(alice) and Agent(bob)): both subagents are invokable. The user does not read this as two overlapping whitelist declarations — it reads as an additive list.

Correct syntax (per official doc): tools: Read, Agent(alice, bob) — single declaration, both subagents invokable.

---

Debug log excerpts (CC 2.1.118)

2026-04-23T02:22:30.912Z [DEBUG] Applying permission update: Adding 98 allow rule(s) to destination 'projectSettings': ["Agent(alice)", …]
2026-04-23T02:22:37.855Z [INFO]  [Stall] tool_dispatch_start tool=Agent toolUseId=toolu_01LXjs… permissionDecisionMs=0
2026-04-23T02:22:37.857Z [WARN]  [Stall] tool_dispatch_end tool=Agent toolUseId=toolu_01LXjs… outcome=error durationMs=2
2026-04-23T02:22:37.858Z [DEBUG] Agent tool error (2ms): Agent type 'alice' has been denied by permission rule 'Agent(alice)' from settings.
2026-04-23T02:22:37.858Z [ERROR] Error: Error: Agent type 'alice' has been denied by permission rule 'Agent(alice)' from settings.

Key observation: permissionDecisionMs=0 — the permission engine itself allowed the call in 0 ms. The denial originates 2 ms later in the Agent-tool dispatch handler (Agent tool error (2ms)), which is a different layer. The error message's "from settings" suffix misleads the user toward permissions.allow/deny inspection.

---

Why this matters

Users who write multi-subagent orchestrators typically reach for the notation tools: Agent(a), Agent(b), Agent(c) by analogy with how other comma-separated values work in the same frontmatter line (e.g., Bash(rg:*), Bash(git:*), Bash(wc:*) — each is a separate allowed pattern). The analogy breaks down silently for Agent(...), where the intended semantics is a single allowlist set, not a composable list of patterns.

The misleading error message compounds the problem: it points to the wrong layer entirely. Users begin debugging settings.json permission configuration when the issue is in the calling agent's frontmatter.

In our case (SENTINELLE multi-specialist pipeline with 7 named subagents), we spent approximately 4 hours on a 10-iteration bisect before an official-doc consultation (triggered by a "verify the doc before filing" discipline check) surfaced the canonical syntax. Full timeline: LEARNING 0003 in our internal knowledge base.

---

Proposed fixes (non-exhaustive, either/both welcome)

Fix 1 — disambiguate the error message

Current:

Error: Agent type 'X' has been denied by permission rule 'Agent(X)' from settings.

Proposed:

Error: Agent type 'X' is not in the invoking agent's `tools:` whitelist. Current whitelist: [<the set>]. Reminder: for multiple subagents, use a single `Agent(x, y, z)` entry in `tools:`, not multiple `Agent(x), Agent(y)` entries.

Or minimally:

Error: Agent type 'X' is not allowed by the invoking agent's `tools:` frontmatter whitelist (not a `settings.json` permission rule).

The critical UX win is removing the "from settings" phrase when the source is not settings.

Fix 2 — parse-time warning on non-documented multi-declaration syntax

When parsing tools:, if more than one Agent(...) declaration is encountered (with at least one having a specifier), emit a warning to stderr or to CC's debug log at startup:

warning: .claude/agents/<name>.md: `tools:` contains multiple `Agent(...)` declarations. Only the last specifier set will apply (e.g., `Agent(a), Agent(b)` ⇒ only `b` is invokable). To whitelist multiple subagents, use a single `Agent(a, b, …)` entry. See https://code.claude.com/docs/en/sub-agents#tools.

Ideally this warning surfaces on first invocation of the Agent tool from that file, not only at parse time, so the developer sees it in context.

Fix 3 (optional, nice-to-have) — reject the syntax entirely

If backward compatibility allows, treat multiple Agent(...) declarations as a parse error rather than silently accepting them. This forces the user to the documented syntax. Breaking change, so lower priority.

---

Related but distinct issues

  • #17853 — TypeError: f.description.split is not a function — distinct (slash-command parser, not subagent tools: frontmatter).
  • #18812 — menu parsing error — distinct.
  • #20264 — bypassPermissions subagent inheritance — distinct layer.
  • #38806 — bypassPermissions 2.1.77+ regressions — distinct.
  • #40580, #34692 — PreToolUse deny not consistently honored on Agent — distinct layer.

None cover the specific UX issues reported here.

---

Impact

Low-severity in terms of system correctness (the parser behaves logically given its input), but measurable developer-time cost when it bites. For a single developer, 4 hours. For a multi-specialist orchestrator project in early iteration, the impact is proportional to how many agents are being defined. The disambiguation fix is localized and should be cheap to ship.

---

Acknowledgments

Reported from the SENTINELLE project (private). Full internal investigation trace preserved in .claude/memory/learnings/0001-cc-agent-tools-frontmatter-bug.md (original, partially superseded) and .claude/memory/learnings/0003-tools-frontmatter-syntax-misread.md (corrected after doc audit).

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗