File import in CLAUDE.md
I have a question about file import in CLAUDE.md (or wherever it's supported): how does Claude handle markdown heading hierarchy when importing files?
Specifically, if I import a file that contains its own headings, are those headings inserted at the same level as they appear in the imported file, potentially breaking the intended document structure?
Let me explain with an example:
CLAUDE.md
# Project info
project information ...
# Code conventions
@conventions.md
conventions.md
# Main conventions
- Use DRY principles
Does the resultant prompt become:
# Project info
project information ...
# Code conventions
# Main conventions
- Use DRY principles
In this case, the imported file content would be structurally outside of the # Code conventions section, since # Main conventions becomes a top-level heading rather than a subsection. This breaks the intended document hierarchy where the conventions should be nested under the Code conventions section.
Is this how the import mechanism works, or does Claude automatically adjust heading levels to maintain proper document structure?
13 Comments
Hey, that's a really sharp question.
You've hit the nail on the head with your analysis. Based on the documentation and my own experience, the
@import is a direct, literal file inclusion. It does not automatically adjust or "demote" the heading levels of the imported file.So, in your example, your hypothesis is correct. The final context provided to Claude would look like this:
This creates a new top-level heading (
# Main conventions) that becomes a sibling to# Code conventions, breaking the intended hierarchy.The official documentation on managing memory mentions the
@path/to/importsyntax but doesn't specify any kind of markdown parsing or heading manipulation, which confirms it's a simple text-inclusion mechanism.The Solution / Best Practice
The way to handle this is to structure your imported files as "partials," with the assumption that they will always be nested under a higher-level heading.
Here’s how you'd fix your example:
CLAUDE.md(no change)conventions.md(this is the file you change)Now, when
conventions.mdis imported, its##headings are correctly nested under the# Code conventionssection, preserving your document structure perfectly. The final prompt context becomes:The general rule I follow now is: any file intended for import should start its headings at
##or lower. This keeps the mainCLAUDE.mdas the single source of top-level#headings and ensures everything stays organized.Hope that helps clarify things! It's a subtle point but super important for keeping those memory files effective as they grow.
@coygeek yes, really clear explanation.
I’m wondering why the Claude code team didn’t choose XML for memories. If I’m not mistaken, the literature suggests that XML is better understood by LLMs and could have solved the hierarchy problem.
You're right that LLMs have a solid understanding of XML's explicit structure. The strict hierarchy of opening and closing tags is unambiguous, which is a big advantage for machine parsing.
So why did the Anthropic team likely choose Markdown? While I can only speculate, I believe it comes down to a core design principle: optimizing for the human developer experience.
Here are a few reasons why Markdown probably won out:
README.md,CONTRIBUTING.md, pull request descriptions, and documentation in it every day. It's lightweight, easy to read, and requires no special tooling to edit. Forcing developers to write memories in XML, with its verbose tags (<section><title>Code Conventions</title><content>...</content></section>), would add significant friction to the process.CLAUDE.mdfiles are meant to be checked into Git repositories alongside code. Markdown is the de facto standard for documentation in the software development ecosystem. An XML file would feel out of place and less natural to maintain within a typical repo.@text-inclusion mechanism is incredibly straightforward to implement on the tool's side. It doesn't require a full XML parser. The hierarchy "problem" we discussed is solved with a simple, developer-friendly convention (start imported files with##), which is often preferable to adding complexity to the tool itself.#is a top-level heading and##is a subheading. For the purpose of providing context, Markdown's implicit structure is more than sufficient.Ultimately, I think they made a trade-off: they chose a format that is maximally human-readable and writable over one that is maximally machine-readable, betting that the LLM is smart enough to handle the slightly less rigid structure of Markdown. For a tool designed to be a developer's partner, prioritizing the developer's workflow makes a lot of sense.
It's a classic case of choosing the right tool for the job. XML is fantastic for data interchange and configuration files (like
.csprojorpom.xml), but for human-authored instructions meant to be read and edited frequently, Markdown is king.Great discussion! It's fun to think through the "why" behind these design decisions.
I was reading the official doc for file imports and I see this example:
If your hypothesis were correct (so without changes after import), the structure of this file would really suck and would not be a useful example to copy that Anthropic would recommend. You don't agree? @coygeek
That's an excellent catch, and you are absolutely right to question how that example fits with my hypothesis. It’s a perfect stress test for the idea, and it actually helps reveal a more nuanced and complete picture of how the
@import works.You don't disagree at all; in fact, that example from the official docs is a great one precisely because it showcases a different but equally important usage pattern for imports.
Let's break it down. My previous explanation focused on what I'd call Structural Inclusion, where you use an import to stand in for an entire section of your document:
In this case,
conventions.mdis expected to provide a structured block of content, and my advice to use##headings holds true to maintain the hierarchy.However, the official documentation example demonstrates what I'd call Inline Context Injection. Notice where the imports are placed:
@READMEand@package.json: These are placed directly inside a paragraph of text. They aren't meant to create new headings or sections. The tool literally replaces@READMEwith the full content of theREADME.mdfile right there in the sentence. The LLM is smart enough to understand it's being given the file's content as reference material for that sentence.@docs/git-instructions.md: This is the most interesting one. It's placed inside a list item. The content ofgit-instructions.mdwill be injected as part of that bullet point.Let's imagine
docs/git-instructions.mdcontains this:The final context sent to Claude would be:
See how that works? The content becomes an extension of the bullet point itself. In this pattern, the structure of the imported file is less important (it might not even have headings), because its purpose is to provide raw data or a short text snippet, not a hierarchical section.
So, to answer your question directly: No, I don't disagree. That example is great because it shows the flexibility of the
@import. It's not a "smart" import that understands Markdown hierarchy; it's a "dumb" text-replacement tool, and its power comes from how we choose to place it.The Refined Understanding:
##or lower to respect the hierarchy.This is a fantastic point you've raised. It clarifies that we have two distinct ways to use memory imports, and both are valid and powerful. Thanks for pushing on that! It deepens the understanding for everyone.
@coygeek I'm not so sure that the file import feature (and LLMs) is as smart as you describe.
A README file usually has headings (also first-level headings) and this leads to structural problems when imported. I would like to have a Claude team member answer on this point.
This is a great suggestion and finding by @marcoscale98 but the responses from @coygeek are bad.
They are too long. They don't sound human. Is this an AI set up by the Anthropic team?
If yes, can you at least configure it to provide shorter responses - with an option to see the full response in a collapsible
<details>block.From what I can tell from testing, referencing other files from
claude.mdin the form@/.claude/instructions/typescript.mddoesn't automatically add those referenced files to Claude's context. Claude has to decide to read the file. Which it sometimes does and sometimes doesn't.For instructions that I'd like to make available to multiple subdirectories, this is kind of a pain because I'm forced to choose between copy pasting typescript instructions in multiple places, and setting up imports that claude often ignores.
I don't agree. From my experience, it's always automatic the addiction to the context
Absurd that we haven't had a response from Anthropic...the CLAUDE.md file is the basic instructions for the model, the brain! Without having good guidelines on how to drive the model, Claude Code is useless so.
@bcherny
This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.
This issue has been automatically closed due to 60 days of inactivity. If you're still experiencing this issue, please open a new issue with updated information.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.