Claude Code read my home folder as a project

Builds on My benchmark read my CLAUDE.md.

Part 5 of this series told the story of OpenCode carrying my global ~/.claude into every bench prompt. It held up Claude Code as the client the harness had isolated properly: CLAUDE_CONFIG_DIR pointed at an empty bench-local directory, and claudeMdExcludes kept the repository's CLAUDE.md out.

NoteCorrection to part 5

That isolation didn't hold. Claude Code walks up from its working directory into my home directory and loads my global CLAUDE.md and ~/.claude/rules/ from there, and every bench workspace sat under it. The details are below.

A stub server settled it. It answers the API with an error and writes every request body to disk. I ran Claude Code against it with the bench's environment variables and flags, from a workspace inside the repository, the way the harness does. The largest request was 146,086 characters. These are the headers of its instruction blocks, contents cut and my home path shortened:

Contents of ~/.claude/CLAUDE.md (project instructions, checked into the codebase):
Contents of ~/.claude/rules/commit-discipline.md (project instructions, checked into the codebase):
[… 14 more ~/.claude/rules/*.md files …]
Contents of <repo>/CLAUDE.md (project instructions, checked into the codebase):

My personal global file arrived labelled as project instructions, "checked into the codebase". The label says which way it came in: as a project file, not as user configuration.

Why moving the config directory wasn't enough#

The harness code knew half of this. Its comment, unchanged since the repository's first commit:

# Isolate from the operator's ~/.claude. That directory carries a ~200-line global CLAUDE.md
# plus rules/, agents/ and skills/, all of which Claude Code injects into every session. Left
# alone, this cell would measure that config rather than the harness, and pi-bare/opencode have
# no equivalent — the comparison would be meaningless. Point at an empty bench-local config.
export CLAUDE_CONFIG_DIR="$ROOT/launch/claude-agent/config"
mkdir -p "$CLAUDE_CONFIG_DIR"
# CLAUDE.md discovery also walks up from the workspace, which sits inside this repo, so the
# repo's own CLAUDE.md reached every claude cell. Exclude it (both /home and /var/home spellings).

The second comment is right about the walk. It stopped thinking about it at the repository root. Claude Code's memory docs say instruction files "in the directory hierarchy above the working directory are loaded at launch", and that "a project CLAUDE.md can be stored in either ./CLAUDE.md or ./.claude/CLAUDE.md". Rules in a .claude/rules/ directory without a paths filter load "with the same priority as .claude/CLAUDE.md".

Put a workspace anywhere under your home directory and walk up:

/var/home/user/                      <- an ancestor of the workspace
├── .claude/
│   ├── CLAUDE.md                    <- user instructions, AND this ancestor's ./.claude/CLAUDE.md
│   └── rules/*.md                   <- this ancestor's project rules
└── work/<repo>/
    ├── CLAUDE.md                    <- excluded by claudeMdExcludes
    └── agent-playground/runs-…/     <- cwd: a bench workspace

One directory, two roles. As the user configuration, ~/.claude is what CLAUDE_CONFIG_DIR replaces, and that part worked: no custom agents or skills showed up. As the .claude folder of an ancestor directory, it's a project, and the config directory has nothing to say about projects. The files load under the second role.

The exclusion worked, for what it named#

The capture also carried the repository's CLAUDE.md, which claudeMdExcludes was supposed to stop. My first note on the leak said the exclusion had failed. That was wrong, and the reason was in the config directory: it has no settings.json. The harness writes that file with the exclusion patterns on every Claude Code run. The stub capture launched Claude Code directly, so it never had the patterns at all.

So on 20 October I ran the capture twice more, Claude Code 2.1.303, a fresh config directory each time, a one-line prompt:

ConfigLargest requestWhat loaded
The harness's four patterns (repo root, /home and /var/home spellings)125,016 chars~/.claude/CLAUDE.md + 15 rules files; no repo CLAUDE.md
The same, plus ~/.claude/** in both spellings73,365 charsno instruction files at all

claudeMdExcludes does what the docs say: "Patterns are matched against absolute file paths using glob syntax." The harness's patterns covered the repository. Nobody had written one for the home directory, because the home directory was supposed to be handled by the config directory.

The sizes are characters of the JSON request body, escaping included, rather than prompt characters. The difference between the two rows is about 52,000 of them.

Which rows it touches#

The bench has 43 Claude Code rows: qwen3-coder-30b 27, ornith-35b 12, the same model with thinking off 3, qwen3-4b 1. Every workspace sat under my home directory, so the walk reached ~/.claude in all of them.

The old runs left their own evidence. The bench config directory still holds the stored sessions, 46 of them from bench workspaces, and 16 record rules files from ~/.claude/rules/ being attached mid-run. Those were java-standards.md when the model opened a Java file, and go-standards.md with crypto-tls.md in the Go tasks. They're path-scoped rules, which load only when matching files are touched, and the session log notes each one. The always-on files left no trace in that older session format, but the walk covers them the same way.

Whether the old runs also had the repository excluded I can't say. The settings file that would show it doesn't exist, and the rows record no client field.

None of the 43 rows feeds a current recommendation. They now count as "Claude Code plus my configuration", a separate condition from the clean harness measurement they were meant to be.

What isolation looks like now#

The frontier reference runs on the bench, Claude Code with Opus 5.5 on two operator tasks, had to be clean, so they used the blunt version: a workspace under /tmp, outside the home tree, and an environment emptied with env -i around a fake home that starts with nothing but a copy of the login credentials, removed after the run. $W is the workspace, $H the fake home, $CL the Claude Code binary, $B the bench directory, $M the run's log directory, $sc the task and $tmo its time limit:

( cd "$W" && env -i HOME="$H" PATH=/usr/bin:/bin TERM=dumb timeout "$tmo" "$CL" -p --strict-mcp-config \
    --model claude-opus-5-5 --dangerously-skip-permissions --output-format stream-json --verbose \
    "$(cat "$B/tasks/$sc.txt")" < /dev/null > "$M/session.jsonl" 2> "$M/stderr.log" )

Nothing above the /tmp workspace holds a CLAUDE.md or a .claude directory, and the fake home held no CLAUDE.md, so no instruction files load, and the six runs' transcripts reference no repository path or scorer. Their session headers list only built-in agents and none of my own skills; they do list skills from plugins synced to the account, which Claude Code downloaded for the account at start-up rather than reading from any file already on this machine. That is the account's configuration rather than this machine's, and it's in every one of those runs alike.

The second row of the table above is the cheaper fix for the harness: one more glob in the settings it already writes. I've measured it for instruction files on a single request, nothing more. The harness change waits until no queue is reading run-scenario.sh.

The label wasn't even false. ~/.claude is a git repository, so that file is checked into a codebase: the one that syncs my configuration between machines, not the one under test. Every bench workspace sat under it.

Filed under: local-llm, llm, ai-agents, benchmarks, claude-code

Sources#

  • Claude Code docs: memory — CLAUDE.md files above the working directory load at launch, ./.claude/CLAUDE.md as a project location, .claude/rules/ priority, and claudeMdExcludes matching absolute paths.
  • Benchmark artifacts (local, not published): the 2026-10-19 stub-server capture (request bodies kept local, they contain private configuration), the 2026-10-20 two-arm claudeMdExcludes probe, run-scenario.sh, the bench's Claude Code config directory and its stored sessions, the results-claude* files, and the frontier reference runner.