Symlinked files in .claude/rules/ are not auto-loaded (contradicts docs)
Version
Claude Code 2.1.238 (claude --version).
Docs claim (code.claude.com/docs/en/memory, quoted verbatim)
"The .claude/rules/ directory supports symlinks, so you can maintain a shared set of rules and link them into multiple projects. Symlinks are resolved and loaded normally."
Observed behavior
A symlink inside .claude/rules/ pointing to a file outside the project directory is not
auto-loaded into context at session start. A plain (non-symlinked) file in the same location loads
correctly. Verified in both a genuine interactive session (claude --dangerously-skip-permissions)
and headless print mode (claude -p), with a neutral, non-leading prompt in both cases.
Repro steps
mkdir -p sandbox/.claude/rules
mkdir -p canonical-source
echo -e '# canonical\nMARKER: ALPHA' > canonical-source/rule.md
echo -e '# sandbox CLAUDE.md\nMARKER: BETA' > sandbox/CLAUDE.md
ln -s "$(pwd)/canonical-source/rule.md" sandbox/.claude/rules/rule.md
cd sandbox
claude -p "List every instruction/rules file you can see loaded, with exact paths and content."
# Expected per docs: both CLAUDE.md and the symlinked rule.md appear as loaded context.
# Observed: only CLAUDE.md is auto-loaded. The symlinked file is not mentioned unless the
# model is separately asked to read the filesystem, at which point it finds the file exists,
# the symlink resolves correctly (`readlink` works), and its content is exactly what was written.
Control
Replacing the symlink with a plain, non-symlinked file of identical content in the same.claude/rules/ location loads correctly (auto-injected, same priority tier as CLAUDE.md,
matches the docs' "Rules without paths frontmatter are loaded at launch with the same priority as.claude/CLAUDE.md"). This isolates the gap to symlinks specifically, not .claude/rules/
loading in general.
Why this matters
The docs present cross-project rule symlinks as the recommended way to share instructions across
multiple projects without the token cost of @path imports (which load the imported file's full
content into context regardless). If symlinks silently don't load, a team relying on this pattern
would have zero indication their shared rules aren't reaching any project — the symlink resolves
fine at the OS level (readlink/ls -la show it correctly), so nothing looks broken from the
filesystem side.
Context this was found in
Investigating whether .claude/rules/ symlinks could de-duplicate boilerplate instructions shared
across multiple CLAUDE.md files in a multi-project setup, using symlinks as the proposed mechanism
per the docs. Found via a deliberate empirical precedence-conflict test in a throwaway sandbox
before relying on the mechanism anywhere real — not found by accident.
7 Comments
I'm experiencing the same issue. This worked previously, some point in the last few weeks I believe is when it stopped.
Same for me on version 2.1.237
It seems the restriction to the same directory also applies for these symlinks as well which would definitely be an issue in the docs:
https://code.claude.com/docs/en/memory#share-rules-across-projects-with-symlinks
<img width="975" height="449" alt="Image" src="https://github.com/user-attachments/assets/43fd43c6-fef4-4d30-9d87-23cb01fea235" />
Our organization followed the recommend approach in the docs stated above to share common rules across many repos. This is a major bug for us, and we would love to see it resolved as soon as possible 🙏
This just bit me. Rules have been silently being ignore for 2 weeks. 2.1.223 is the last working version that I have locally that behaves as it's described to.
Investigation by Claude:
Bisected further and read the loader in the 2.1.246 bundle — here's the mechanism.
Still broken on 2.1.243, 2.1.245 and 2.1.246 (same test as above, headless: a random token planted in
the rule file that the model can only print if the file was loaded). On 2.1.246 specifically:
.claude/rulesitself a symlink to outside the project; a symlinked subdirectory inside areal
.claude/rules; a symlinked file inside a real.claude/rules..claude/rules(control); symlinks under~/.claude/rules(user scope); and —inconsistently — a whole-directory
.claude -> <outside>symlink whose contents are real files.Mechanism:
includeExternal= the project'shasClaudeMdExternalIncludesApprovedflag from~/.claude.json— the same flag theexternal-
@-import approval dialog sets.lstats as a symlink and resolves outside the working directoryreturns nothing, and a symlinked entry inside a real rules directory that resolves outside is skipped.
Nothing is logged or shown.
~/.claude/rules) passesincludeExternal: trueunconditionally, which is whythose symlinks keep working.
CLAUDE.md@-import resolves outside the project. The ruleswalk never raises it, so a rules symlink has no route to becoming approved — except as a side effect of
also having an external
@-import (reading the code, approval would unblock the rules walk too; notverified empirically).
.claudesymlink slips through because the checklstats only therulesentry,not its parents.
There is no CHANGELOG entry for this between 2.1.223 (last known good here) and 2.1.237 (first reported
bad above); the only rules-symlink line anywhere is 2.1.239's
claudeMdExcludesfix.If the external gate is intentional — a committed symlink in a cloned repo could pull files from outside
the checkout into context — then what would make it livable is: route rules symlinks through the same
approval dialog as
@-imports, print a startup notice whenever a rules entry is skipped, and update thedocs. Otherwise, restoring the documented behavior for project-scope symlinks.
The problem was introduced in 2.1.232.
I detected it with an
InstructionsLoadedhook that appends one line per instruction file the harness loads.Each release is installed by version:
The same prompt runs on every release, in a fresh session:
When it is not working, the log holds only the session-start line:
I tested from 2.1.236 downwards and it is working on 2.1.231:
Confirming @nicholasshirley mechanism and @ARivottiC bisection independently, plus an additional data point:
Windows repro: same behavior reproduced on Windows: intra-repo symlink loads, cross-repo symlink silently skipped. Not just a Linux/macOS issue.
Workaround, confirmed working: triggering the external-import approval dialog on the target project flips
hasClaudeMdExternalIncludesApprovedtotruefor that project entry in~/.claude.json, which immediately unlocks the previously-skipped rule symlinks. No other change needed. Concretely: add a temporary@/absolute/path/outside/project.mdimport to a project-scope memory file, open a session, accept the approval dialog, remove the line.This confirms the fix target precisely: whatever changed in 2.1.232, decoupling
includeExternalfor the project-scope rules walk fromhasClaudeMdExternalIncludesApproved(or making the approval dialog actually fire for rules, instead of silently skipping) resolves it without touching the user-scope path, which is unaffected.The
hasClaudeMdExternalIncludesApprovedworkaround does not cover path-scoped rules.I applied it as @lcristin describes: a
@/absolute/path/outside/project.mdimport in the projectCLAUDE.md, approval dialog accepted. Every rule in my.claude/rules/is a symlink to a shared directory outside the project, and the approval did take effect — the rules that carry nopaths:frontmatter now load again, where before none of them did.The rules with
paths:frontmatter still never load. Same hook and same prompt as my earlier comment, on 2.1.251, with the approval in place:/linked/rule.mdis scopedpaths: [".notes/**"]. The prompt reads two files under.notes/and nopath_glob_matchline appears for either of them./linked/rule3.mdis path-scoped too, to.other. It is in the session only because it is the file theCLAUDE.md@-import points at, and it loads without anything under.otherbeing read. So an approved external@-import loads a path-scoped rule unconditionally, while the glob walk that should load one on demand still fires nothing.Without the workaround, the link problem is still not fixed in 2.1.251.