Skip to content

@import Composition Pattern for Instruction Files

Claude Code supports @path/to/file imports in CLAUDE.md, enabling modular instruction authoring. Other major agent tools do not — they rely on hierarchical discovery instead.

How Claude Code Imports Work

CLAUDE.md files can reference other files with @path/to/file syntax. At session start, Claude Code expands all imports and loads them into context (docs).

Rules:

  • Both relative and absolute paths are supported
  • Relative paths resolve from the importing file, not the working directory
  • Imports nest up to five levels deep
  • First encounter triggers an approval dialog; declined imports stay disabled and the dialog does not reappear (docs)
# CLAUDE.md

See @README for project overview and @package.json for available npm scripts.

## Additional Instructions
- Git workflow: @docs/git-workflow.md
- Code style: @docs/code-style.md

# Individual Preferences
- @~/.claude/my-project-preferences.md

Each referenced file is pulled in verbatim — semantically equivalent to concatenation at session start.

Known Limitation: Tilde Expansion

As of late 2025, @~/.claude/file.md references (tilde home-directory expansion) are silently not loaded in some configurations (Issue #8765). Use absolute paths as a workaround:

# Works
@/home/username/.claude/my-preferences.md

# May silently fail
@~/.claude/my-preferences.md

When to Use Imports vs .claude/rules/

Imports and .claude/rules/ both modularise instructions. The distinction is load timing:

Mechanism When it loads Best for
@path imports At session start, always Core project context shared across all files
.claude/rules/*.md without paths At session start (same as CLAUDE.md) Topically organised rules that always apply
.claude/rules/*.md with paths frontmatter On demand, when matching files are opened Language- or directory-specific conventions

Use imports when you want to pull external content (README, package.json, a canonical AGENTS.md) into CLAUDE.md without duplicating it. Use .claude/rules/ when you want path-scoped rules that activate only when the agent works in a specific area.

DRY Instruction Authoring

The main use case for imports is a single-source-of-truth for project conventions shared across tools or across team members:

project-root/
├── AGENTS.md                      # canonical project context (tool-agnostic)
├── CLAUDE.md                      # imports AGENTS.md, adds Claude-specific config
└── .github/
    └── copilot-instructions.md    # manual copy — Copilot has no import support

CLAUDE.md imports the canonical file:

@AGENTS.md

## Claude Code
Use [worktrees](../workflows/worktree-isolation.md) for experimental work.
Check subdirectory CLAUDE.md files — auth code has additional constraints.

.github/copilot-instructions.md must duplicate the shared content because Copilot has no equivalent syntax. The drift surface is small and explicit.

User-specific preferences stay out of version control by importing from ~/.claude/:

# CLAUDE.md (checked in)
@~/.claude/my-project-preferences.md

The import reference is checked in; the file it points to stays local. Teammates who lack the file see the import silently skipped.

Cross-Tool Comparison

Tool File inclusion syntax Mechanism for modularity
Claude Code @path/to/file in CLAUDE.md (docs) Import expansion at load time
Claude Code (alternative) .claude/rules/*.md with paths frontmatter Path-scoped rules, demand-loaded
GitHub Copilot None Hierarchical discovery: nested AGENTS.md, applyTo globs in .github/instructions/
OpenAI Codex None Directory traversal + concatenation root-down
Cursor @file within .cursor/rules/*.mdc Context attachment (referenced file appended as context at rule evaluation, not expanded inline into the rule body)

Failure mode for unsupported tools: @AGENTS.md in a Copilot instructions file is not a supported directive — it passes through as literal Markdown text. The model may attempt to interpret it as a file path or ignore it — there is no error.

Example: Shared Base with Tool-Specific Extends

A team maintains a shared shared/base-instructions.md that both a project CLAUDE.md and a user CLAUDE.md import:

project-root/
├── CLAUDE.md
└── shared/
    └── base-instructions.md    # shared conventions (not a CLAUDE.md, just a .md file)
# shared/base-instructions.md

## Commit format
Use Conventional Commits. Types: feat, fix, docs, chore, refactor.

## Testing
Run `pnpm test` before committing. All tests must pass.
# CLAUDE.md
@shared/base-instructions.md

## Project-Specific
- API handlers live in `src/api/`; one file per resource
- Use `zod` for all input validation
# ~/.claude/CLAUDE.md  (user scope, not version-controlled)
@shared/base-instructions.md

## Personal preferences
- Prefer concise responses without preamble

Both CLAUDE.md files stay short; shared content lives once.

When This Backfires

  • Silent broken imports: renaming or moving an imported file breaks the reference without any error. Claude silently loads fewer instructions than expected — the failure is invisible.
  • Approval-dialog friction: the first-use approval dialog blocks imports in headless or CI contexts where there is no interactive session to click through.
  • Nesting limit: import chains are capped at five levels. A deeply composed instruction set that exceeds this limit is truncated at load time with no warning.
  • Tilde expansion is unreliable: @~/.claude/file.md silently fails in some configurations (closed as NOT_PLANNED: Issue #8765); absolute paths are the only reliable workaround.

Key Takeaways

  • Claude Code's @path import syntax is shipped and documented — imported files load at session start alongside CLAUDE.md
  • Tilde expansion (@~/...) is unreliable; use absolute paths for home-directory imports
  • Imports and .claude/rules/ are complementary, not alternatives — imports for external content, rules for path-scoped conventions
  • GitHub Copilot, OpenAI Codex, and AGENTS.md standard do not support file inclusion — modularity comes from hierarchical discovery, not imports
  • Unsupported @-syntax in other tools is not processed — it either appears as literal text or is silently ignored, with no error
Feedback