[BUG] claude -p: @-mention on the first turn loads CLAUDE.md and rules that are already in context a second time
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
In print mode (claude -p), an @-mention of a file on the first turn triggers the nested-memory walk for the mentioned file's directories, and that walk loads again every CLAUDE.md and unconditional .claude/rules/*.md from those directories that is already in the startup context. The file paths are identical, yet the copies are not deduplicated. The model receives the same instructions twice: once in the startup instructions and once as nested memory.
The same @-mention does not duplicate anything:
- on a later turn of the same headless session (
-c/--resume); - when the model reaches the file with the Read tool instead of an
@-mention; - in an interactive session, including its first message.
So the dedup exists and works. It is only missing on the first headless turn.
What Should Happen?
sub/CLAUDE.md and sub/.claude/rules/style.md are already loaded, so the nested walk adds only the new file sub/inner/CLAUDE.md:
{"file_path":".../CLAUDE.md","load_reason":"session_start"}
{"file_path":".../sub/CLAUDE.md","load_reason":"include"}
{"file_path":".../sub/.claude/rules/style.md","load_reason":"session_start"}
{"file_path":".../sub/inner/CLAUDE.md","load_reason":"nested_traversal"}
Error Messages/Logs
{"file_path":".../CLAUDE.md","load_reason":"session_start"}
{"file_path":".../sub/CLAUDE.md","load_reason":"include"}
{"file_path":".../sub/.claude/rules/style.md","load_reason":"session_start"}
{"file_path":".../sub/CLAUDE.md","load_reason":"nested_traversal"}
{"file_path":".../sub/.claude/rules/style.md","load_reason":"nested_traversal"}
{"file_path":".../sub/inner/CLAUDE.md","load_reason":"nested_traversal"}
The session transcript (~/.claude/projects/<dir>/<session>.jsonl) shows the same thing. SUB-1 and RULE-1 appear both in the instructions attachment and in a nested_memory attachment. INNER-1 appears once, as nested_memory.
Note that the hook reports the startup path of the symlinked rule as its resolved path, sub/.claude/rules/style.md. That is the same string as the nested candidate, so this is not a symlink-path mismatch.
Steps to Reproduce
In an empty directory:
mkdir -p sub/inner sub/.claude/rules .claude/rules
printf 'Root token: ROOT-1\n\n@sub/CLAUDE.md\n' > CLAUDE.md
printf 'Sub token: SUB-1\n' > sub/CLAUDE.md
printf 'Inner token: INNER-1\n' > sub/inner/CLAUDE.md
printf 'Rule token: RULE-1\n' > sub/.claude/rules/style.md
ln -s ../../sub/.claude/rules/style.md .claude/rules/style.md
echo hello > sub/inner/notes.txt
cat > .claude/settings.json <<'EOF'
{"hooks":{"InstructionsLoaded":[{"hooks":[{"type":"command","command":"jq -c '{file_path,load_reason}' >> \"$CLAUDE_PROJECT_DIR/il.log\""}]}]}}
EOF
claude -p "Summarize @sub/inner/notes.txt in one line."
cat il.log
So at startup sub/CLAUDE.md is in context through the root import, and sub/.claude/rules/style.md through the symlinked rule. The symlink is optional; it only shows that rules are affected too. The InstructionsLoaded hook is just an observer.
Controls (same files, fresh directory each):
| Run | Extra nested_traversal for sub/CLAUDE.md / style.md | sub/inner/CLAUDE.md loaded |
|---|---|---|
| claude -p "Summarize @sub/inner/notes.txt in one line." (bug) | 2 | yes |
| claude -p "Use the Read tool on sub/inner/notes.txt and summarize it in one line." | 0 | yes |
| claude -p "Reply with the single word OK." then claude -p -c "Summarize @sub/inner/notes.txt in one line." | 0 | yes |
In every run sub/inner/CLAUDE.md loads, so the nested walk did run each time and the zeros in the controls are real. An interactive session with the same @-mention in its first message also shows no duplicate (checked on the same version).
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.283 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Non-interactive/CI environment
Additional Information
Likely cause (from reading the 2.1.283 native bundle; names are as they appear there)
The nested-memory filter skips a candidate in three cases: its path is in loadedNestedMemoryPaths, its path is in readFileState, or its content matches the snapshot. The snapshot is built from the last instructions attachment in the message history.
- In the interactive REPL,
onInitcallsseedMemoryFilefor every startup memory file, which puts it intoreadFileStateandloadedNestedMemoryPaths. The headless path has no such call. - On the first turn,
@-mentions are processed before the query loop adds theinstructionsattachment, so there is no snapshot yet.
On the first headless turn all three checks fail, and every candidate in the mentioned file's directories is loaded again. On later turns the snapshot exists, which is why -c / --resume does not duplicate.
Possible fixes: seed the startup memory files in the headless path as the REPL does, or give the mention-attachment pass a snapshot of the startup instruction files.
Impact
- Every first-turn
@-mention inclaude -por SDK automation pays for the duplicated instructions again. In a real project with a shared instructions subdirectory this added roughly 94k to 360k characters to the first request. - Duplicate instructions can also change how the model weighs them.
- Probe or eval scripts that use
claude -pwith@measure a context the interactive user never gets.
Workaround: on the first -p turn, write the path as plain text and let the model use Read, or move the @-mention to a follow-up turn.
Environment
- Claude Code 2.1.283 (native build, linux-x64), Linux 6.8
- Auth: claude.ai subscription login; model
claude-opus-5-5 - Not tested: the Agent SDK. It uses the same headless entrypoint (
sdk-cli), so it is probably affected too.
Related issues (different mechanism, not duplicates)
- #59105:
claude -pruns are classified assdk-cliand diverge from the TUI. - #23565, #46222: memory files and rules loaded twice in git worktrees (duplicate discovery through two paths).
- #94463:
nested_traversalloads aCLAUDE.mdtwice when two path spellings reach it (symlink). A comment there (2.1.278) traces the same missing headlessseedMemoryFiledescribed above, reached through Read with the home directory as cwd. This report is a different trigger: an@-mention on the first-pturn with ordinary, same-path project files, where canonical path keys would not help.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗