[Bug] Agent tool registry fails to discover subset of valid .claude/agents/*.md files

Status Open
Reported on v2.1.220
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 29, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Bug Description
Agent tool's subagent_type registry only exposes a subset of custom agents defined in .claude/agents/*.md.

Setup: project has 15 agent definition files in .claude/agents/ (all valid, same format, same directory). Only 8 of them show up as dispatchable subagent_types via the Agent tool: gray, holt, nolan, pax, quill, reeve, sable, wren.

Missing (files exist on disk, never appear as a subagent_type): auden, iris, marlow, prue, reid, sloane, vance.

Notable: after the gap was first noticed (7 of 14 agents missing at the time), a new agent (holt) was created mid-session and registered successfully without any restart. So new hires can register live — it's specifically this fixed set of 7 pre-existing agents that never registers, not a general "mid-session additions don't load" issue.

Tried: fully quit all Claude Code processes/windows (confirmed via Get-Process claude returning nothing), relaunched, and used claude --resume to reopen the same session. Same 7 agents still missing after the full cold restart — identical missing set before and after. Also reproduced identically in both the VS Code integrated terminal and a standalone Windows Terminal / PowerShell session — same 7 agents missing in both, ruling out a terminal-specific or IDE-integration cause. Rules out simple session-cache/snapshot-lag as the explanation, since both a live mid-session add (holt) worked and a full cold restart didn't fix the other 7.

Environment: Claude Code CLI 2.1.220, Windows 11 Pro (build 10.0.26200), win32 platform, tested in both windows-terminal and VS Code integrated terminal. Related feedback ID from in-app /bug report: c11d04a8-92c1-4b7f-868d-4b94eebce23f.

Expected: all 15 files in .claude/agents/ should register as dispatchable subagent_types.
Actual: only 8 do, consistently, across restarts, across terminals, and across a live mid-session addition.

View original on GitHub ↗

3 Comments

bcherny collaborator · 14 days ago

Thanks for the detailed report. I tried to reproduce on the released 2.1.233 (macOS) with a project containing 15 files in .claude/agents/, all valid, and all 15 showed up as subagent types. So a fixed subset going missing points at something specific to those 7 files rather than a general cap on discovery.

Three things I did reproduce that make a file silently disappear from the agent list on 2.1.233 (no error shown, and the file otherwise looks identical):

  • The file starts with a UTF-8 byte-order mark (BOM) or is saved as UTF-16. Windows PowerShell 5.1's Out-File/Set-Content -Encoding UTF8 and some editors write a BOM by default. The frontmatter is then not recognized and the agent is skipped with no message at all — that's a bug on our side (it should at least be reported), and we'll look at making BOM'd files load.
  • Two files declare the same name: in their frontmatter (e.g. a copied template) — only one loads.
  • Broken YAML in the frontmatter (this one does show up in claude --debug output as "failed to parse").

Could you check the 7 missing files: run Format-Hex .claude\agents\auden.md -Count 8 (a leading EF BB BF or FF FE is the BOM/UTF-16 case), confirm each name: value is unique across all 15 files, and run claude --debug and look for lines mentioning "Duplicate agent name" or "frontmatter ... failed to parse"? If none of those apply, please paste one of the missing files verbatim (frontmatter included) so we can dig further.

🤖 Generated with Claude Code

github-actions[bot] · 14 days ago

We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected? This issue will be closed automatically if there's no activity within 7 days.

mTw76 · 9 days ago

Thanks — your third cause was the right one, and running --debug as you suggested is what cracked it. I have a root cause and a minimal reproduction that does not require Windows.

Summary: this is not agent discovery. It is YAML frontmatter parsing, and the behaviour is line-ending dependent. An unquoted description: plain scalar containing : (colon followed by a space) parses fine when the frontmatter block uses LF, and fails to parse when the same frontmatter block uses CRLF. The affected file is then dropped from the agent registry with no message anywhere outside --debug. That asymmetry is why it does not reproduce on macOS.

---

Your three checks

| # | Check | Result |
|---|---|---|
| 1 | UTF-8 BOM / UTF-16 | Ruled out. All 18 files begin 2d 2d 2d 0d 0a or 2d 2d 2d 0a (---). No BOM, no UTF-16 — on the registered files and the missing files alike. |
| 2 | Duplicate name: values | Ruled out. All 18 name: values are unique, and every one matches its own filename stem. No Duplicate agent name line appears in --debug. |
| 3 | Broken YAML frontmatter | Confirmed — this is the cause. --debug emits exactly 10 warnings, naming exactly the 10 agents that fail to register. |

claude --debug in the real project, filtered:

[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\holt.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\auden.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\prue.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\vance.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\vera.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\iris.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\sloane.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\reid.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\marlow.md: YAML Parse error: Unexpected token
[WARN] Failed to parse YAML frontmatter in ...\.claude\agents\ward.md: YAML Parse error: Unexpected token

Ten warnings, ten missing agents, exact set match. Apologies for filing this as a discovery/registry bug — the warning was there the whole time and I had never run --debug.

That said, I think there is still a real defect here, described in "What I think the actual bug is" below.

---

Isolating the trigger

I built seven agent files that are identical except for one controlled property each, and ran a fresh claude -p ... --debug-file against them.

| Agent file | Frontmatter line endings | Body line endings | description: contains | Registers? |
|---|---|---|---|---|
| alphaclean.md | CRLF | CRLF | no : | yes |
| bravocolon.md | CRLF | CRLF | unquoted : | no |
| charlielf.md | LF | LF | unquoted : | yes |
| deltaquoted.md | CRLF | CRLF | : inside a double-quoted scalar | yes |
| echonospace.md | CRLF | CRLF | : with no following space | yes |
| foxtrotlffm.md | LF | CRLF | unquoted : | yes |
| golfcrlffm.md | CRLF | LF | unquoted : | no |

Only bravocolon.md and golfcrlffm.md produced a Failed to parse YAML frontmatter warning, and only those two were absent from the agent list.

So the trigger is precisely:

CRLF line endings inside the frontmatter block and an unquoted plain scalar containing colon-space.

Three independent things each fix it: quote the scalar, remove the colon-space, or switch the frontmatter to LF. Body line endings are irrelevant — foxtrotlffm (LF frontmatter, CRLF body) registers and golfcrlffm (CRLF frontmatter, LF body) does not.

---

Why this did not reproduce for you

description: some text: more text is, strictly speaking, invalid YAML regardless of line endings — a plain scalar cannot contain : . Both PyYAML and a standards-conformant JS parser reject it either way.

But Claude Code only rejects it under CRLF. Under LF it accepts the same logical content. My working hypothesis — and I have not read your source, so treat this as a pointer rather than a claim — is that there is a fast-path extraction for the common key: value frontmatter shape that succeeds under LF and never invokes the real YAML parser, and that a trailing \r on each line causes that fast path to miss and fall through to the strict parser, which then correctly rejects the colon-space. Whatever the mechanism, the observable behaviour is a hard platform split.

The practical consequence is the part I would flag: the same git commit produces a different set of registered agents on Windows than on macOS/Linux. Any repo cloned with core.autocrlf=true (the Windows default), or any agent file written by a Windows editor, gets CRLF frontmatter and starts silently losing agents whose descriptions contain ordinary prose punctuation. On macOS the identical file works. That makes it invisible to a maintainer testing on macOS, and invisible in code review, since the offending byte is \r.

In my case one file, miles.md, happens to have been written with LF-only line endings while the other 17 are CRLF — and miles.md registers correctly despite having a colon-space in its description. It was the sole outlier, and it is what pointed at line endings in the first place.

---

What I think the actual bug is

Two things, both small:

  1. Inconsistent parser strictness across line endings. Whether Claude Code should accept description: a: b is your call — but it should give the same answer on Windows and macOS. Right now identical content behaves differently, and the difference is an invisible character.
  2. The failure is silent at default verbosity. A malformed agent file produces no output at startup, and the agent simply does not appear in the Agent tool's subagent_type list or in /agents. The [WARN] only exists under --debug. Since the warning is already being generated, surfacing it once at startup — or listing the file in /agents as failed-to-load with its parse error — would turn a multi-week mystery into a five-second fix. That is the change I would most like to see.

A validation lint (claude agents check, or a warning when /agents is opened) would cover it even more cleanly.

---

Minimal reproduction — macOS/Linux, no Windows required

The \r is what matters, so printf is used to make the line endings explicit:

mkdir -p /tmp/ccrepro/.claude/agents && cd /tmp/ccrepro

# A - CRLF frontmatter, description contains NO colon-space
printf '---\r\nname: alphaclean\r\ndescription: Test agent A. Plain description with no colon followed by a space.\r\ntools: Read\r\nmodel: inherit\r\n---\r\n\r\n# alphaclean\r\n' > .claude/agents/alphaclean.md

# B - CRLF frontmatter, description contains ONE colon-space
printf '---\r\nname: bravocolon\r\ndescription: Test agent B. Use this agent for testing: it has a colon followed by a space.\r\ntools: Read\r\nmodel: inherit\r\n---\r\n\r\n# bravocolon\r\n' > .claude/agents/bravocolon.md

# C - byte-identical description to B, but LF frontmatter
printf '---\nname: charlielf\ndescription: Test agent C. Use this agent for testing: it has a colon followed by a space.\ntools: Read\nmodel: inherit\n---\n\n# charlielf\n' > .claude/agents/charlielf.md

claude -p "List every subagent_type value available to your Agent tool. Output only the values, comma-separated, nothing else." --debug-file /tmp/ccrepro/debug.log

grep "Failed to parse YAML frontmatter" /tmp/ccrepro/debug.log

Expected (buggy) result: alphaclean and charlielf both appear in the listed subagent_type values; bravocolon does not. grep returns exactly one line, naming bravocolon.md.

B and C differ only in line endings. That difference alone decides whether the agent exists.

---

Environment

  • Claude Code CLI: 2.1.228 (the original report said 2.1.220; behaviour is unchanged between them)
  • OS: Windows 11 Pro, build 10.0.26200
  • Agent files in .claude/agents/: 18
  • Register successfully (8): gray, miles, nolan, pax, quill, reeve, sable, wren
  • Do not register (10): auden, holt, iris, marlow, prue, reid, sloane, vance, vera, ward
  • All 18 name: values unique and matching their filenames; no BOM on any file; 17 of 18 files are CRLF throughout, miles.md is LF-only.

---

Correction to my original report

My original report speculated that newly created agent files register correctly while already-broken ones stay broken. That is wrong and I withdraw it. Two agents created after filing (vera, ward) registered at first and are now in the failing set, and one (holt) was working in July and later stopped. In every case the actual change was an edit that introduced a colon-space into the description: field — nothing to do with when the file was created. There is no time-based or ordering component to this at all.

I also want to record a hypothesis I tested and disproved, in case it saves anyone else the detour: file size and description length are not the discriminator, despite correlating suspiciously well. The largest registered file is 16,234 bytes and the smallest failing one is 10,340 bytes; the longest registered description: is 1,595 characters and the shortest failing one is 1,228. Both distributions overlap and neither is separable. Long descriptions simply have more opportunities to contain a colon.

---

Smallest failing file, verbatim

.claude/agents/reid.md — the frontmatter block, byte-for-byte (the file's remaining ~10 KB is ordinary Markdown prose below the closing --- and plays no part in the parse; every line below ends CRLF):

---
name: reid
description: General design engineer for the PKA_Demo AI team — design and build treated as the same act, done in the same medium, at the same time. Standing duty (added 2026-07-31, per Thilo's direct decision): equal-standing co-fix authority alongside Iris on issues she encounters in the data/larry.db viewer — the two work out per issue whether Iris attempts first with Reid reviewing after (mode a, default for small/clear-cut issues), they address it together from the start (mode b), or both attempt independently and reconcile if they land on different answers (mode c) — default to b or c for genuinely ambiguous/tricky issues, whoever hits the issue first calls the mode, open to the other pushing back if they disagree with that call. Iris remains the viewer's sole, standing owner and final decision-maker on its overall direction and scope (2026-07-24 decision unchanged); this gives Reid real fix-authority on individual issues, not passive advisory-only commentary and not a reversal of her ownership. Reid remains available for other design-engineering work as it arises — new local tools, other data-model-to-UI mapping, stack/architecture decisions, front-end builds — anywhere the job is genuinely "design and build together." Do not use Reid for unilateral calls on the viewer's overall direction/scope (that's Iris's call) or for content/voice decisions about journal/CRM/meeting data (that's Wren's domain).
tools: Read, Write, Edit, Glob, Grep
model: inherit
---

The offending sequence is at character 197 of the description: per Thilo's direct decision): equal-standing — a colon followed by a space, in ordinary English prose, in an unquoted scalar. Under LF this file loads. Under CRLF it does not.

For completeness, the first colon-space in each of the other nine failing files is the same shape — normal prose punctuation, e.g. governed config: classify, business intelligence: owns, brought to Thilo: verifies. Nothing exotic, and nothing a reasonable author would expect to be a syntax error.

---

Happy to test a patch build, run any additional variant you want, or supply the full --debug log. Please reopen if useful — the platform-dependent parse and the silent drop both seem worth fixing independently of my own malformed files.