Imports and External Context
You can pull other files into a session's memory without copying them in — and see exactly when Claude asks before trusting a file from outside your project.
Learning Objectives
- Compose a
CLAUDE.mdfrom several files with@pathimports - Predict the context cost of an import — and why it isn't cheaper
- Recognise when an import is external, and what approval it needs
- Avoid the two syntax traps: spaces and quotes
- Explain why an import from your home directory is safer than a committed one
The Problem
You split CLAUDE.md into tidy topic files, then copied them back in so the agent would see them. Now there are two copies of every file, and they drift. You fix a convention in the topic file and forget the copy, and the agent follows the stale one.
Then the other problem. A teammate commits a CLAUDE.md with a line that reaches into their home directory for a config file. You open the repo and Claude asks whether it can read a file outside your project. You don't know what that file is, or why a project you cloned wants it. You approve without looking, or you decline and wonder what broke.
Both come from the same feature: @path imports.
The Concept
A CLAUDE.md can pull in other files with @path/to/import syntax. The imported file is expanded and loaded into context at launch, alongside the CLAUDE.md that references it — as if you had typed its contents in.
flowchart TD
A["CLAUDE.md"] -->|"@docs/conventions.md"| B["docs/conventions.md"]
A -->|"@README"| C["README"]
B -->|"@more.md (up to 4 hops)"| D["more.md"]
B --> E["All expanded at launch"]
C --> E
D --> EThe rules:
- Relative paths resolve relative to the file containing the import, not the working directory. An import in
docs/guide.mdof@setup.mdlooks fordocs/setup.md. - Recursion goes up to four hops. Imported files can import other files, to a depth of four.
- Import parsing skips code spans and fenced code blocks. To mention a path without importing it, wrap it in backticks: `
@READMEstays literal,@README` imports. - Spaces need backslashes. Put a
\before each space:@Design\ Docs/api-conventions.md. Without them the path ends at the first space. - Quotes break it. A path wrapped in quotes is not imported at all, with or without backslashes.
Imports do not save context
This is the part people get wrong. Splitting a long CLAUDE.md into imports makes it easier to organise — but every imported file still loads at launch. The cost is the same, and each imported file counts as a separate file against the combined-size warning. If the goal is a smaller window, imports are the wrong tool; path-scoped rules (Lesson 67) are the right one.
The reframe: an import is not a link the agent follows when it needs to — it is the file's contents pasted into memory at launch, so it organises your writing without shrinking your context, and an import that reaches outside your project is the moment the trust decision moves to you.
External imports and the approval dialog
An import is external when its path resolves outside your working directory — the home-directory example above, for instance. The first time Claude Code sees external imports in a project, it shows an approval dialog listing the files. Decline and the imports stay disabled; the dialog doesn't appear again.
User-scope memory files are treated differently. ~/.claude/CLAUDE.md and ~/.claude/rules/ are files you wrote, so their imports load without the dialog — except in Cowork sessions on your desktop, which skip any import that resolves outside the session's working directory.
That asymmetry is the reason an import from ~ is safer than one committed by someone else: a committed import into a teammate's home directory is a file the repo asks you to trust, and the dialog is the repo asking.
Symlinks are imports
A symlink under .claude/rules/ whose target is outside your working directory is treated like an external import: it doesn't load until you approve external imports for the project, and after that only the rules without a paths: field load. Rules in ~/.claude/rules/ avoid the prompt because they apply to every project anyway.
Related switches
claudeMdExcludesskips specificCLAUDE.mdand rules files by path or glob — useful when an ancestorCLAUDE.mdimports things you don't want.--add-dirgives Claude access to a directory outside the working directory, but itsCLAUDE.mddoes not load unless you setCLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1.AGENTS.mdimports are expanded too, and aclaudeMdExcludespattern applies to them.
Build It
- Create
docs/conventions.mdwith a convention. Reference it fromCLAUDE.mdwith@docs/conventions.md. Start a fresh session and run/context.
✅ You pass when both files appear under Memory files, as separate files.
- Move
CLAUDE.mdinto a subdirectory and import@conventions.md(no path prefix). Observe which file is found.
✅ You pass when you can state that the path resolved relative to the importing file, not the working directory.
- Import a file from a folder whose name has a space, using backslashes before each space.
✅ You pass when the file loads and you can say what happens without the backslashes.
- Wrap the same import in quotes and restart.
✅ You pass when you can explain why the quoted path was not imported.
- Add an import that resolves into your home directory to a project
CLAUDE.md. Restart and read the approval dialog. Decline, then restart again.
✅ You pass when you can say which files the dialog listed, and that it did not appear a second time after you declined.
Use It
| Command / concept | When you reach for it |
|---|---|
@path/to/file | Compose a CLAUDE.md from several files |
| Relative path | Resolves against the importing file, not the working directory |
` @README ` | Mention a path without importing it |
@Design\ Docs/file.md | Import a path containing spaces |
| Four hops | The recursion depth limit for imports |
| External import dialog | Approve files a project reaches for outside your working directory |
claudeMdExcludes | Skip a CLAUDE.md or rules file by path or glob |
--add-dir + env var | Load memory files from an additional directory |
Exercises
- Build a
CLAUDE.mdthat is entirely imports — one file for commands, one for conventions, one for architecture. Then measure its context cost against a single file with the same content. ✅ You pass when you can show the imported version is not smaller, and explain why the split still helped you.
- A teammate's committed
CLAUDE.mdimports a file from their home directory. Decide whether to approve it. Write down what you would check first. ✅ You pass when you can name the risk (a file you didn't write, reaching into a repo you cloned) and the safe alternative (keep personal imports in your own user-scope file).
- You want your personal project instructions to work across every worktree of a repo. Set it up without committing anything. ✅ You pass when a fresh session in a second worktree loads your notes and
git statusshows nothing new to commit.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| import | "a link" | The file's contents expanded into context at launch, not fetched on demand. |
| external import | "a file outside the project" | An import whose path resolves outside your working directory. It needs approval once. |
| four hops | "recursive imports" | Imported files may import others, to a depth of four. |
| backtick trick | "escaping" | ` @README stays literal; only a bare @README` imports. |
| import cost | "it's split up now" | The same as one file — every import loads at launch. Organisation, not economy. |
| user-scope imports | "trusted" | Imports in ~/.claude/CLAUDE.md and ~/.claude/rules/ load without the dialog (except in Cowork). |
What this lesson skipped
CLAUDE.mdmechanics — load order, the 200-line rule,/context. Lesson 65.- Rules — the path-scoped alternative when you want a smaller window. Lesson 67.
AGENTS.md— which file loads, and imports inside it. Lesson 66.- The combined-size warning and trimming a bloated context. Phase 13.
- Managed and org-wide instruction files. Phase 17.
The behaviour above is from Import additional files. The external-import dialog and the Cowork exceptions are version- and surface-gated; check the page before relying on an exact behaviour.
Check yourself
4 questions. Answer before you reveal — recognising the right option is not the same as being able to produce it.
-
You split a 400-line
CLAUDE.mdinto five files and import them all. The session starts and/contextshows all five. What did you gain and what did you not?Show answer
B. You gained organisation, but the context cost is unchanged — every import loads at launch
Imports are expanded at launch, so the total context is the same; only the organisation improved. Option 0 is tempting because 'split into files' sounds like lazy loading — but imports are not fetched on demand; path-scoped rules are.
-
Your
CLAUDE.mdat the repo root has the line@docs/setup.md. A teammate copies that line intodocs/guide.md. Which file does it now import?Show answer
B.
docs/docs/setup.md, because the path resolves relative todocs/guide.mdRelative paths resolve against the file containing the import, so
docs/guide.mdplusdocs/setup.mdgivesdocs/docs/setup.md. Option 0 is the tempting wrong answer because the working directory is the usual base for paths — imports use the importing file instead. -
You clone a repo whose
CLAUDE.mdimports a file from the author's home directory. Claude asks you to approve external imports. What is the safe move?Show answer
B. Decline unless you have read the file; the repo is asking you to trust something you did not write
An external import in a committed file reaches outside your project and should be read before you trust it. Option 0 is tempting because the dialog is a normal step — but it appears precisely because the file is not something you wrote.
-
You want to mention
@READMEin yourCLAUDE.mdas an example, without actually importing the file. What do you write?Show answer
B. Wrap it in backticks: `
@README`Import parsing skips code spans, so a backticked
@READMEstays literal. Option 2 is tempting because quotes feel like escaping — but a quoted path is simply not imported and the quotes remain in the text, which is not the same as leaving the path literal.