Project-scope agent in .claude/agents/ intermittently fails to register at session start (same file, same version — nondeterministic)
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.195→2.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.mdregistered 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 foundon spawn, andcar-coldabsent 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-coldwas still absent in the next fresh session. Reverted. - Version change —
2.1.196on 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
- 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? - Is
tools: [](an intentionally empty allowlist) a supported configuration? Is there a documented/canonical way to express "this agent gets no tools"?
5 Comments
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-coldis 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.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: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-coldandprobe-emptyare 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 emptytools: []allowlist as the shared trigger, independent of the rest of thecar-colddef's content.We've now added a third control,
probe-anchor, identical in shape toprobe-emptybut with a minimal non-empty repo-inert allowlist (tools: [WebSearch]) — differing in exactly one variable. If it registers reliably in the sessions where bothtools: []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.mdand recreated it byte-for-byte (verified:git hash-objectbefore == after ==1130aac5, working tree clean). Across three subsequent turn boundaries,car-coldnever 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. Andprobe-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
tools: []allowlist (finding 1).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?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/*.mdthat 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 viaautocrlfon 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-cloneis a byte-for-byte copy of a known-registering def (playwright-test-planner.md) with onlyname:changed — created LF-preserving. It registers.model:/color:fields, description length, file size, and load/sort order.git ls-files --eol+ raw0x0Dcount): 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=trueand no.gitattributesrule pinning*.mdtoeol=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
.gitattributesrule forcing LF for agent defs, and renormalize the working tree: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:
.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.---
DRAFT — closing comment for anthropics/claude-code#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/*.mdthat 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 viaautocrlfon 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-cloneis a byte-for-byte copy of a known-registering def (playwright-test-planner.md) with onlyname:changed — created LF-preserving. It registers.model:/color:fields, description length, file size, and load/sort order.git ls-files --eol+ raw0x0Dcount): 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=trueand no.gitattributesrule pinning*.mdtoeol=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
.gitattributesrule forcing LF for agent defs, and renormalize the working tree: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:
.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.---
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/*.mdthat 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 viaautocrlfon 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-cloneis a byte-for-byte copy of a known-registering def (playwright-test-planner.md) with onlyname:changed — created LF-preserving. It registers.model:/color:fields, description length, file size, and load/sort order.git ls-files --eol+ raw0x0Dcount): 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=trueand no.gitattributesrule pinning*.mdtoeol=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
.gitattributesrule forcing LF for agent defs, and renormalize the working tree: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:
.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.---