DEV Community

Rulestack
Rulestack

Posted on

CLAUDE.md @-imports: how paths resolve, how deep they go, and why yours silently did not load

You split a long CLAUDE.md into pieces and pulled them back in with @ imports. Now half of it doesn't seem to reach Claude, and nothing tells you which half.

Four rules in the import mechanism explain almost every case of this. None of them produce an error message.

What an import actually does

@path/to/import inside a CLAUDE.md expands the referenced file and loads it into context at launch, alongside the CLAUDE.md that references it.

That word — launch — is the first thing people get wrong. Splitting a large memory file into imports does not reduce how much context you spend. The docs say it plainly: imports help organization, but imported files load at launch either way. If your actual goal is a smaller prompt, imports are the wrong tool; path-scoped rules that only load when Claude touches matching files are the right one.

1. Relative paths resolve from the importing file, not your working directory

Relative paths resolve relative to the file containing the import, not the working directory.

So docs/ai/CLAUDE.md containing @guides/style.md looks for docs/ai/guides/style.md — no matter which directory you started claude from.

This bites hardest in monorepos, where the same import line gets copy-pasted into a nested CLAUDE.md one level deeper and quietly resolves somewhere else.

2. Recursion stops at four hops

Imported files can import other files, up to a maximum depth of four hops.

If you've built an index-of-indexes — root memory imports a manifest, which imports per-area manifests, which import the actual rules — you can run out of depth before reaching the file that holds your content. Nothing warns you. The rules simply aren't there.

Flatten the chain, or move the deep files up.

3. Backticks switch imports off

Import parsing skips Markdown code spans and fenced code blocks. Writing `@README` keeps the text literal; writing @README outside backticks imports the file.

That cuts both ways:

  • Mentioning a path in prose without backticks silently imports it. See @package.json for the scripts doesn't mention package.json — it loads the whole file into every session.
  • Wrapping an intended import in backticks disables it. Easy to do by habit, and easy for a formatter or a well-meaning reviewer to do for you.

If you're documenting a path, wrap it. If you're importing one, don't.

4. Imports pointing outside the project need approval — and you only get asked once

An import in a project-level memory file counts as external when its path resolves outside your working directory. The classic example is sharing personal instructions across git worktrees:

# Individual Preferences
- @~/.claude/my-project-instructions.md
Enter fullscreen mode Exit fullscreen mode

The first time Claude Code meets external imports in a project, it shows an approval dialog listing the files. If you decline, the imports stay disabled and the dialog does not appear again.

This is the rule behind most "but it works on my machine" reports. The dialog exists to protect you from files other people commit to a shared repo, so it's the correct default — but a single declined prompt months ago is indistinguishable, from the outside, from a broken path.

Imports inside user-scope memory (~/.claude/CLAUDE.md, ~/.claude/rules/) don't trigger the dialog. Those are files you wrote yourself.

The AGENTS.md case, since everyone hits it

Claude Code reads CLAUDE.md, not AGENTS.md. If your repo already has AGENTS.md for other agents, the documented pattern is a CLAUDE.md that imports it and then appends whatever is Claude-specific:

@AGENTS.md

## Claude Code

Use plan mode for changes under `src/billing/`.
Enter fullscreen mode Exit fullscreen mode

A symlink works too, if you don't need to add anything:

ln -s AGENTS.md CLAUDE.md
Enter fullscreen mode Exit fullscreen mode

It prints nothing on success — so confirm it in the next session with /context and look for CLAUDE.md under Memory files. On Windows, symlinks need Administrator privileges or Developer Mode, so prefer the import there.

Personal instructions, and the worktree trap

CLAUDE.local.md at the project root loads alongside CLAUDE.md and is treated the same way. Add it to .gitignore so it never gets committed.

One catch: because it's gitignored, a CLAUDE.local.md only exists in the worktree where you created it. If you work across several worktrees of the same repo, import a file from your home directory instead — which puts you back in rule 4, approval dialog included.

A five-step check when an import didn't land

  1. Resolve the path from the importing file's directory, not from where you launched Claude.
  2. Count the hops. More than four and the tail is gone.
  3. Grep for backticked @. Also grep for un-backticked ones in prose you didn't mean to import.
  4. Does any path leave the project? If so, you may have declined the approval dialog — on this machine, once, some time ago.
  5. Run /context and read what's actually listed under Memory files. That's the only answer that isn't a guess.

Step 5 is the one worth building a habit around. Every other step is a theory about what loaded; /context is the observation.


Behaviour above verified against the Claude Code memory documentation on 2026-08-15. If it changes, the /context check still tells you the truth.

I write one of these a day on how coding agents actually read your config. If that's useful, Rulestack on Bluesky is where they go up first.

Ready-made rule and skill packs: rulestack.gumroad.com

Top comments (0)