Symlinked rule directories load from user-level ~/.claude/rules/ but never from project-level .claude/rules/ (2.1.241, 2.1.251)
Summary
Symlink resolution in the rules loader behaves differently depending on scope:
- User-level
~/.claude/rules/: a symlinked directory is resolved and loaded correctly. ✅ - Project-level
.claude/rules/: symlinks are never loaded at all. ❌ Neither symlinked files nor symlinked directories; neither at startup (no frontmatter) nor via thepaths:lazy-injection trigger when a matching file is read.
The exact same rule content, as the exact same kind of symlink, works at user level and is silently ignored at project level. Replacing the project-level symlink with a plain cp -r copy makes it load again — so specifically project-level symlink resolution is broken.
Environment
- Claude Code 2.1.241 and 2.1.251 (both reproduced)
- macOS (Darwin 24.6.0, arm64)
- Verified with fresh headless sessions (
claude -p) using marker strings
Minimal reproduction (symmetric A/B/C)
# Shared probe content, outside any project
mkdir -p ~/shared-rules/myteam
printf 'If this rule is loaded, the marker SYMLINK_RULE_PROBE_7f3a is present.\n' \
> ~/shared-rules/myteam/probe.md
ASK='check whether your instructions contain SYMLINK_RULE_PROBE_7f3a. Answer yes/no only.'
# A) User level, symlinked directory → LOADS
ln -s ~/shared-rules/myteam ~/.claude/rules/myteam
claude -p "$ASK" # → yes
rm ~/.claude/rules/myteam
# B) Project level, same symlink → DOES NOT LOAD
cd /path/to/project && mkdir -p .claude/rules
ln -s ~/shared-rules/myteam .claude/rules/myteam
claude -p "$ASK" # → no
# C) Project level, plain copy instead of symlink → LOADS AGAIN
rm .claude/rules/myteam
cp -r ~/shared-rules/myteam .claude/rules/myteam
claude -p "$ASK" # → yes
Only the location (user vs project) and the symlink-vs-copy difference change; everything else is identical.
The paths: variant is also affected
Rules with paths: frontmatter are lazily injected when a matching file enters context. This works for regular files in project .claude/rules/, but a symlinked directory containing such rules is never injected, even when a matching file (e.g. src/example.ts for **/*.ts) is read:
claude -p "Read @src/example.ts, then $ASK" # symlinked dir → no; copied dir → yes
Symlinked files (.claude/rules/foo.md -> ~/shared-rules/foo.md) behave the same way: not loaded when symlinked, loaded when copied. The failure is independent of the symlink target location (home dir, /tmp, and other paths were all tested).
Behavior matrix (all verified empirically)
| Layout | Loaded? |
|---|---|
| user ~/.claude/rules/subdir (symlinked dir) | ✅ |
| project .claude/rules/*.md (regular file) | ✅ |
| project .claude/rules/subdir/*.md (regular dir, recursed) | ✅ |
| regular file with paths: + matching file read | ✅ (lazy injection works) |
| project .claude/rules/foo.md (symlinked file) | ❌ |
| project .claude/rules/subdir (symlinked dir) | ❌ |
| …symlinked dir + paths: rule + matching file read | ❌ (no injection) |
Expected behavior
The memory docs describe sharing rules across projects with symlinks ("Share rules across projects with symlinks"), with examples of symlinking directories from outside the project into .claude/rules/. The changelog also shows symlinked rules being actively maintained (2.1.239/2.1.240 fixes for claudeMdExcludes matching on symlinked .claude/rules files), which implies they are supposed to load. User-level symlink loading working confirms the intent — project level should behave the same.
Relationship to previous issues
- #764 was closed as completed on 2.1.233 after the maintainer could not reproduce — but the verified scenario there was a user-level
~/.claudesymlink (stow) with commands, which is exactly the scope that still works here. The broken case is the project-level.claude/rules/scope, which that verification did not cover. - #25903 (user-level symlinked rule file +
paths:not loaded) was closed as a duplicate of #764 by the reporter.
So there is currently no open issue tracking the project-level .claude/rules/ symlink case.
Testing note
When reproducing, make sure no stale copies of the rule files remain inside .claude/rules/ (e.g. renamed backups): the loader recurses into regular subdirectories, and leftover copies will make a symlink test falsely pass.