CLAUDE.md @import cascades into plain relative-path mentions inside imported files, ignoring non-`@` syntax and "do not auto-load" notes
BODY:
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (closest match is #23952, a feature request asking Anthropic to add auto-loading of referenced files — which implies this is not supposed to be the current default behavior)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.236, stable channel)
What's Wrong?
When a project's root CLAUDE.md is read for the first time in a session and it uses the documented @path/to/file.md import syntax, the imported file's own plain, non-@ relative file-path mentions (e.g. a path written as plain text or inside a markdown table cell, with or without backtick code-span formatting) also get recursively resolved and injected as additional context blocks — exactly as if they had been @-imported themselves.
This happens even though:
- these mentions have no
@prefix at all —@-import is documented as an explicit, opt-in line-start syntax, not something triggered by any relative-path-looking string appearing anywhere in a file's prose - the surrounding text can explicitly say not to load the file by default (e.g. "authoritative source is
X.md(do not always-load it, it's 67KB)") — that instruction is ignored and the file gets injected anyway
We could reproduce this reliably with a multi-row markdown table where each row's "detail" column contains a relative path ending in .md. We also have one real-world observation (see "Additional notes") of a single, non-tabular prose mention triggering the same behavior, though we couldn't cleanly isolate that case in a controlled multi-trial test (see the "once per session" note below).
What Should Happen?
Only files referenced via the literal @path import syntax should be auto-loaded. Plain-text or backtick-quoted relative-path mentions inside an imported file's prose/tables should not themselves trigger further loading — that's exactly why @import exists as an explicit syntax rather than "any string that looks like a path".
Steps to Reproduce
In a brand-new session (this matters — see notes below), before doing anything else, create these files and then Read the root CLAUDE.md:
CLAUDE.md:
# Test root
@rules-index.md
rules-index.md:
# Rules index
> Detail files are meant to be read on-demand, one at a time, only when working on that specific item.
| # | name | detail |
|---|---|---|
| 1 | a | .claude/rules/apps/row-01.md |
| 2 | b | .claude/rules/apps/row-02.md |
| 3 | c | .claude/rules/apps/row-03.md |
| 4 | d | .claude/rules/apps/row-04.md |
| 5 | e | .claude/rules/apps/row-05.md |
| 6 | f | .claude/rules/apps/row-06.md |
| 7 | g | .claude/rules/apps/row-07.md |
| 8 | h | .claude/rules/apps/row-08.md |
| 9 | i | .claude/rules/apps/row-09.md |
| 10 | j | .claude/rules/apps/row-10.md |
| 11 | k | .claude/rules/apps/row-11.md |
| 12 | l | .claude/rules/apps/row-12.md |
.claude/rules/apps/row-01.md ... row-12.md: each a one-line file with a unique marker string (e.g. MARKER_ROW_01).
- As the very first CLAUDE.md-related operation in a fresh session, run
Read("<path>/CLAUDE.md"). - Expected: the tool result is followed by exactly one additional injected block — the contents of
rules-index.md(the literal@import). - Actual: the tool result is followed by 13 additional injected blocks —
rules-index.md, plus all 12row-01.md...row-12.mdfiles — even though none of them were@-imported, only mentioned as plain text in a table cell, and the file explicitly said these are meant to be read one at a time on-demand. - We repeated the test with the exact same table but with backticks added around each path (`
.claude/rules/apps/row-01.mdinstead of plain text) — identical result, all 12 injected. So the trigger is not markdown code-span formatting specifically; it appears to be "relative-path-shaped string with a recognized extension (here.md`), appearing multiple times in a table" (or possibly any occurrence at all — see notes below).
Additional notes
- Real-world case (larger and with a non-tabular trigger too): in an actual project with an extensively cross-referenced
.claude/rules/documentation tree (deliberately split into many small files, each meant to be read only when working on that specific task, per the project's own documented conventions), reading the rootCLAUDE.mdfor the first time in a session pulled in on the order of 100 additional markdown files (254 individual path mentions resolving to 100 unique files across the reachable@importgraph) — roughly 830KB / ~200k tokens injected in one turn. One of the pulled-in files (67KB) was referenced by a single, non-tabular sentence that explicitly said "do not always-load this file" right next to its path — and it still got loaded. - Apparent "fires once per session" cap: re-
Read-ing the identicalCLAUDE.mdfile later in the same session does not repeat the cascade (no additional blocks are injected the second time). Testing two different, never-before-touched@import-bearingCLAUDE.mdfiles back-to-back in the same session showed the first one's@importgot resolved, but the second (read immediately after) did not resolve its@importat all. This made it hard to run more than one clean, isolated trial per session — if you try to reproduce this, do it as the very first CLAUDE.md-touching operation in a fresh session, or you may get a false negative. - Environment: Claude Code 2.1.236, stable channel, Windows. This was observed inside a session with an extended toolset beyond the stock CLI (workflow orchestration, artifact publishing, scheduled wakeups, remote triggers, etc.), so we can't fully rule out this being specific to that particular build rather than the public CLI everyone uses — flagging that uncertainty rather than asserting it's universal. Happy to help narrow it down further if that's useful.
- Related but distinct issues we found: #23952 (feature request for auto-loading referenced files — implies current behavior shouldn't do this), #29971 (general context-bloat tracker covering skill duplication / MCP overhead / CLAUDE.md re-injection-per-tool-call — different mechanisms than what's described here).
Impact
For any project that deliberately splits detail into many small files behind an index/summary table (a pattern the docs and many style guides recommend specifically to keep default context small — "read the detail file only when you need it"), simply touching the root CLAUDE.md for the first time in a session defeats that design entirely and can, by itself, push a session's context past the ~200k-token pricing threshold before any actual work has begun.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗