Project-scope agent in .claude/agents/ intermittently fails to register at session start (same file, same version — nondeterministic)

Status Fixed / completed
Reported on v2.1.196
Maintainer reply None cached
Activity 5 comments · opened Jul 18, 2026 · closed Jul 21, 2026

Summary

A project-scope agent definition registers normally in one session, then fails to register in later fresh sessions — with the file byte-identical and the Claude Code version unchanged. When it fails, spawning the agent errors with Agent type '<name>' not found and the type is absent from the session's available-agents list. Sibling agent defs in the same directory register every time.

Environment

  • Claude Code 2.1.196 (npm-global install)
  • Windows 10 (win32), PowerShell + Git Bash
  • Last Claude Code self-update: 2026-06-29 (2.1.1952.1.196), per ~/.claude/.last-update-result.json. No update since — the same 2.1.196 ran on both the working and failing days.

Symptom

  • Agent def .claude/agents/car-cold.md registered normally in a session on 2026-07-17 (the agent spawned successfully).
  • It then failed to register in 6 consecutive fresh sessions on 2026-07-18: Agent type 'car-cold' not found on spawn, and car-cold absent from the session's available-agents list.
  • Restarting Claude Code did not reliably fix it (the failures span multiple fresh launches on the same day).

The file is byte-identical across the boundary (git-proven)

The def has exactly one commit touching it and was not modified between the working and failing sessions. The Git blob hash is identical at every relevant commit and at HEAD:

$ git rev-parse eccee95b:.claude/agents/car-cold.md
1130aac56c26aa3c68be36179676c627b738dde1
$ git rev-parse 4cfcd0fa:.claude/agents/car-cold.md      # merge to main
1130aac56c26aa3c68be36179676c627b738dde1
$ git rev-parse HEAD:.claude/agents/car-cold.md
1130aac56c26aa3c68be36179676c627b738dde1

$ git diff eccee95b 4cfcd0fa -- .claude/agents/car-cold.md
(empty — no diff)

Same on-disk bytes in the working session and all five failing sessions.

Sibling defs in the same directory register fine

Other agent defs in the same .claude/agents/ directory (playwright-test-planner, playwright-test-generator, playwright-test-healer) register successfully in every session, including the ones where car-cold is missing. So general project-agent discovery is working — only this one def intermittently drops.

Ruled out empirically

  • Plugin conflict — disabled our only agent plugin (agent-skills@addy-agent-skills) at project scope in .claude/settings.json; car-cold was still absent in the next fresh session. Reverted.
  • Version change2.1.196 on both the working and failing days (see Environment); no auto-update across the boundary.

One structural oddity worth your attention

car-cold is our only agent def with an empty tools allowlist (tools: []). This is semantically required for our use case — a deliberately tool-less reviewer that can judge only the text pasted into its prompt (no repo/shell/delegation tools by construction). If empty-allowlist parsing has a nondeterministic code path (e.g. an empty array treated as missing/invalid and the def silently dropped on some load ordering), that's a candidate for the flakiness.

Full frontmatter (verbatim)

---
name: car-cold
description: Repo-BLIND cold acceptance reviewer (T-518/D-084). Runtime agent type for the CAG cold reviewers (CAR, Cold Spec Review, acceptance-attack). Its tools: allowlist is EMPTY — it EXCLUDES every repo-reading and shell capability (no Read, Grep, Glob, Bash, LS, Edit, Write) AND the Agent/Task delegation tools (so it cannot spawn a sighted child to read the repo by proxy), so repo-blindness is structural — the agent has no tool with which to open a repository file. It judges ONLY the text pasted into its prompt and emits StructuredOutput (auto-provided by the schema option). Spawned only by the .claude/workflows cold-review scripts, never directly.
tools: []
model: sonnet
color: purple
---

Impact

This agent is our review gate — a deliberately repo-blind reviewer our acceptance pipeline spawns to check work before it merges. When car-cold fails to register, the acceptance pipeline is down for the whole session, and the failure is silent until a spawn is attempted.

Questions

  1. Is registration of agent defs in .claude/agents/ at session start expected to be deterministic given identical on-disk files? If so, what could cause a single def to intermittently not register while its siblings do?
  2. Is tools: [] (an intentionally empty allowlist) a supported configuration? Is there a documented/canonical way to express "this agent gets no tools"?

View original on GitHub ↗

5 Comments

SMGreenRepos · 1 month ago

Update (2026-07-18): the intermittent non-registration has now persisted across 7 consecutive fresh sessions — one more than the 6 noted in the original report. Same conditions each time: car-cold is absent from the session's available-agents list at start, the def on disk is byte-identical (same git blob), Claude Code 2.1.196 is unchanged, and the sibling .claude/agents/ defs register normally in the very same sessions. No manual change on our side between sessions.

SMGreenRepos · 1 month ago

Three new findings since the original report

We've kept observing this across additional fresh sessions and instrumented it with control agents. Three findings that should materially narrow the search:

(1). Both of our empty-allowlist agents drop together (lockstep) at cold start

We added a trivial diagnostic twin, probe-empty, with the same empty allowlist as the failing agent:

---
name: probe-empty
description: diagnostic registration-control twin (referenced by nothing, spawned by nothing)
tools: []
model: sonnet
---

It is referenced by no workflow and spawned by nothing — it exists only to be read from the session's available-agents list. In every non-registering session since, both car-cold and probe-empty are absent together — they flicker in lockstep. Meanwhile sibling defs in the same directory with non-empty allowlists (playwright-test-planner/generator/healer) register in those same sessions. This points at the empty tools: [] allowlist as the shared trigger, independent of the rest of the car-cold def's content.

We've now added a third control, probe-anchor, identical in shape to probe-empty but with a minimal non-empty repo-inert allowlist (tools: [WebSearch]) — differing in exactly one variable. If it registers reliably in the sessions where both tools: [] agents are absent, that both confirms the empty-allowlist trigger and validates a non-empty allowlist as the workaround. (Data forthcoming.)

(2). A byte-identical delete + recreate does NOT re-register an already-known path (only restart works)

Within a failing session we deleted .claude/agents/car-cold.md and recreated it byte-for-byte (verified: git hash-object before == after == 1130aac5, working tree clean). Across three subsequent turn boundaries, car-cold never registered. So the "touch/recreate the file" workaround does not clear an already-failed registration for a given path — a full Claude Code restart is the only thing that clears it. This suggests the registration decision for a known path is made once at session load and is not revisited on later filesystem changes to that same path.

(3). Dynamic filesystem pickup is real but transient/unstable in both directions

The registry-update channel is demonstrably live during these sessions: creating a brand-new agent path (probe-empty) pushed an "agent added" notification mid-session, and deleting it pushed an "agent removed" notification. But that same live channel never emitted a "car-cold now available" notification for the already-known path in finding #2. And probe-empty's own dynamic registration was transient — it registered on creation, then dropped again later in the same session with no file change. So dynamic pickup fires for new paths but is unstable, and does not rescue an already-known path that failed to register at load.

Net

  • The def is byte-constant across working and failing sessions (git-proven, from the original report), so this is not a frontmatter-content problem.
  • The one structural variable that co-varies with the failure is the empty tools: [] allowlist (finding 1).
  • Registration for a known path appears decided at first load and not re-evaluated on later same-path filesystem changes (finding 2), while new paths are picked up dynamically but unstably (finding 3).

Question

Does the loader treat an empty tools: [] array differently from a populated one (e.g. empty-array-as-missing/invalid, dropped on some load orderings), and is registration for a given agent path memorized at session start such that a later byte-identical rewrite of that path won't re-trigger it?

SMGreenRepos · 1 month ago

Root cause found: CRLF line endings in an agent-definition file silently break its registration.

Summary: on Windows with core.autocrlf=true, an agent definition under .claude/agents/*.md that lands in the working tree with CRLF line endings is silently skipped at agent-type registration — the type never appears in the session's available-agent list, and there is no warning or error. The identical file with LF endings registers normally. This fully explains the non-deterministic registration we reported earlier in this issue: it was never non-deterministic per session — it tracked the on-disk line endings of each def, which drifted to CRLF via autocrlf on some checkouts.

Evidence — a 4-arm controlled experiment

All four are trivial agent defs in the same .claude/agents/ directory, in the same session:

| Arm | Line endings | tools: format | Registered? |
|-----|--------------|-----------------|-------------|
| car-cold (the real one) | CRLF | flow-array [] | ❌ absent |
| probe-empty (control) | CRLF | flow-array [] | ❌ absent |
| probe-anchor (control) | CRLF | flow-array [WebSearch] | ❌ absent |
| probe-clone (control) | LF | bare comma-list | ✅ present |

  • probe-clone is a byte-for-byte copy of a known-registering def (playwright-test-planner.md) with only name: changed — created LF-preserving. It registers.
  • The three CRLF arms register together in lockstep across many fresh sessions and never register at cold start.
  • Ruled out as the discriminator (both groups share these, or they don't split cleanly): BOM (none present), model:/color: fields, description length, file size, and load/sort order.
  • Ground-truth byte check (git ls-files --eol + raw 0x0D count): the three absent arms are pure-CRLF (19 / 12 / 13 CR bytes); every registered def is pure-LF (0 CR bytes). A perfect CRLF-absent / LF-present split.

The common trigger on Windows: a repo with core.autocrlf=true and no .gitattributes rule pinning *.md to eol=lf. Files authored or checked out as CRLF in the working tree then fail to register, while older LF files in the same directory keep working — which reads exactly like non-determinism.

Our fix (workaround)

Add a .gitattributes rule forcing LF for agent defs, and renormalize the working tree:

.claude/agents/*.md text eol=lf

After that, the previously-CRLF defs register at cold start.

Request

The workaround shouldn't be necessary, and the silent-skip is the real hazard. Please either:

  1. Make the agent-def frontmatter loader tolerant of CRLF (normalize line endings before parsing), or
  2. At minimum, emit a LOUD warning when a def in .claude/agents/ is found but skipped (parse failure, CRLF, malformed frontmatter, etc.) — anything but a silent omission from the available-agent list.

A silent skip means a gate/agent that a workflow depends on can be un-runnable in a session with no indication why. A one-line "skipped .claude/agents/car-cold.md: <reason>" at startup would have turned a multi-session investigation into a 10-second diagnosis.

---

SMGreenRepos · 1 month ago

DRAFT — closing comment for anthropics/claude-code#78917

DRAFT ONLY. Steve posts this himself. CC does not submit to GitHub without explicit per-action authorization ([[feedback_no_boundary_crossing_without_authorization]]). Paste the body below (between the rules) as a new comment on #78917.

---

Root cause found: CRLF line endings in an agent-definition file silently break its registration.

Summary: on Windows with core.autocrlf=true, an agent definition under .claude/agents/*.md that lands in the working tree with CRLF line endings is silently skipped at agent-type registration — the type never appears in the session's available-agent list, and there is no warning or error. The identical file with LF endings registers normally. This fully explains the non-deterministic registration we reported earlier in this issue: it was never non-deterministic per session — it tracked the on-disk line endings of each def, which drifted to CRLF via autocrlf on some checkouts.

Evidence — a 4-arm controlled experiment

All four are trivial agent defs in the same .claude/agents/ directory, in the same session:

| Arm | Line endings | tools: format | Registered? |
|-----|--------------|-----------------|-------------|
| car-cold (the real one) | CRLF | flow-array [] | ❌ absent |
| probe-empty (control) | CRLF | flow-array [] | ❌ absent |
| probe-anchor (control) | CRLF | flow-array [WebSearch] | ❌ absent |
| probe-clone (control) | LF | bare comma-list | ✅ present |

  • probe-clone is a byte-for-byte copy of a known-registering def (playwright-test-planner.md) with only name: changed — created LF-preserving. It registers.
  • The three CRLF arms register together in lockstep across many fresh sessions and never register at cold start.
  • Ruled out as the discriminator (both groups share these, or they don't split cleanly): BOM (none present), model:/color: fields, description length, file size, and load/sort order.
  • Ground-truth byte check (git ls-files --eol + raw 0x0D count): the three absent arms are pure-CRLF (19 / 12 / 13 CR bytes); every registered def is pure-LF (0 CR bytes). A perfect CRLF-absent / LF-present split.

The common trigger on Windows: a repo with core.autocrlf=true and no .gitattributes rule pinning *.md to eol=lf. Files authored or checked out as CRLF in the working tree then fail to register, while older LF files in the same directory keep working — which reads exactly like non-determinism.

Our fix (workaround)

Add a .gitattributes rule forcing LF for agent defs, and renormalize the working tree:

.claude/agents/*.md text eol=lf

After that, the previously-CRLF defs register at cold start.

Request

The workaround shouldn't be necessary, and the silent-skip is the real hazard. Please either:

  1. Make the agent-def frontmatter loader tolerant of CRLF (normalize line endings before parsing), or
  2. At minimum, emit a LOUD warning when a def in .claude/agents/ is found but skipped (parse failure, CRLF, malformed frontmatter, etc.) — anything but a silent omission from the available-agent list.

A silent skip means a gate/agent that a workflow depends on can be un-runnable in a session with no indication why. A one-line "skipped .claude/agents/car-cold.md: <reason>" at startup would have turned a multi-session investigation into a 10-second diagnosis.

---

SMGreenRepos · 1 month ago

Root cause found: CRLF line endings in an agent-definition file silently break its registration.

Summary: on Windows with core.autocrlf=true, an agent definition under .claude/agents/*.md that lands in the working tree with CRLF line endings is silently skipped at agent-type registration — the type never appears in the session's available-agent list, and there is no warning or error. The identical file with LF endings registers normally. This fully explains the non-deterministic registration we reported earlier in this issue: it was never non-deterministic per session — it tracked the on-disk line endings of each def, which drifted to CRLF via autocrlf on some checkouts.

Evidence — a 4-arm controlled experiment

All four are trivial agent defs in the same .claude/agents/ directory, in the same session:

| Arm | Line endings | tools: format | Registered? |
|-----|--------------|-----------------|-------------|
| car-cold (the real one) | CRLF | flow-array [] | ❌ absent |
| probe-empty (control) | CRLF | flow-array [] | ❌ absent |
| probe-anchor (control) | CRLF | flow-array [WebSearch] | ❌ absent |
| probe-clone (control) | LF | bare comma-list | ✅ present |

  • probe-clone is a byte-for-byte copy of a known-registering def (playwright-test-planner.md) with only name: changed — created LF-preserving. It registers.
  • The three CRLF arms register together in lockstep across many fresh sessions and never register at cold start.
  • Ruled out as the discriminator (both groups share these, or they don't split cleanly): BOM (none present), model:/color: fields, description length, file size, and load/sort order.
  • Ground-truth byte check (git ls-files --eol + raw 0x0D count): the three absent arms are pure-CRLF (19 / 12 / 13 CR bytes); every registered def is pure-LF (0 CR bytes). A perfect CRLF-absent / LF-present split.

The common trigger on Windows: a repo with core.autocrlf=true and no .gitattributes rule pinning *.md to eol=lf. Files authored or checked out as CRLF in the working tree then fail to register, while older LF files in the same directory keep working — which reads exactly like non-determinism.

Our fix (workaround)

Add a .gitattributes rule forcing LF for agent defs, and renormalize the working tree:

.claude/agents/*.md text eol=lf

After that, the previously-CRLF defs register at cold start.

Request

The workaround shouldn't be necessary, and the silent-skip is the real hazard. Please either:

  1. Make the agent-def frontmatter loader tolerant of CRLF (normalize line endings before parsing), or
  2. At minimum, emit a LOUD warning when a def in .claude/agents/ is found but skipped (parse failure, CRLF, malformed frontmatter, etc.) — anything but a silent omission from the available-agent list.

A silent skip means a gate/agent that a workflow depends on can be un-runnable in a session with no indication why. A one-line "skipped .claude/agents/car-cold.md: <reason>" at startup would have turned a multi-session investigation into a 10-second diagnosis.

---