Why your second Claude Code agent speaks as your first
You set HOME=~/agents/beta. You start a second claude process. Within five minutes, the second agent is signing messages with the first agent’s name.
This is the most disorienting failure mode in multi-agent Claude Code setups, because every part of the configuration looks right. The plist sets HOME. The process inherits it. printenv HOME from inside the running session returns the override. And yet the agent reads from the system user’s CLAUDE.md and auto-memory, and starts wearing the wrong persona.
The root cause is one call to os.homedir() that ignores your environment. Here is the full diagnosis, the minimal fix, and the three follow-up mistakes that quietly re-introduce the leak.
What you actually see
The symptom looks like this. You have an agent named alpha in ~/agents/alpha/.claude/CLAUDE.md:
You are alpha. You manage frontend tasks. Your tone is concise and pragmatic. Sign off as `— alpha`.
You set up a second agent beta the same way: ~/agents/beta/.claude/CLAUDE.md with a different persona, different sign-off, different tone. You launch beta with HOME=~/agents/beta claude.
The first message you exchange with beta:
Sure, I’ll take a look at the API endpoint and report back.
— alpha
Beta is reading alpha’s persona. Or rather: beta is reading the system user’s CLAUDE.md, which happens to be alpha’s, because you set up alpha first.
If you had three agents, all three would speak as whoever was instantiated most recently in the system home. The tone differences from your per-agent CLAUDE.md files don’t show up at all.
The diagnosis: load order with one ignored env var
Claude Code resolves CLAUDE.md from two locations and stacks them:
- Global:
~/.claude/CLAUDE.md— the system user’s home. - Project:
<cwd>/.claude/CLAUDE.md— wherever the process was started.
Project rules win on conflict. Auto-memory loads similarly: it lives at ~/.claude/projects/<cwd-slug>/memory/.
Both lookups go through os.homedir() (or getpwuid(getuid())), and that call does not honor $HOME. It reads the system password database for the running UID and returns that user’s home directory. Setting HOME=~/agents/beta in the environment changes what ~ expands to in your shell, what node’s process.env.HOME reports, and what most user-land tools see — but os.homedir() looks past all of that.
So when you launch beta with HOME=~/agents/beta, here’s what actually happens:
cwdis~/agents/beta(because that’s where you ran the command from, or because the plist setWorkingDirectory).- Project
CLAUDE.mdresolves to~/agents/beta/.claude/CLAUDE.md✅ - Global
CLAUDE.mdresolves toos.homedir() + '/.claude/CLAUDE.md'=/Users/<system-user>/.claude/CLAUDE.md❌ - Auto-memory writes to
os.homedir() + '/.claude/projects/-Users-system-user-agents-beta/memory/'❌
If you hadn’t bothered with a project CLAUDE.md (because you assumed HOME would do the work), beta now reads only the global file — alpha’s persona — and beta becomes alpha.
If you did set up a project CLAUDE.md for beta, beta still reads alpha’s global file first, then stacks beta’s project rules on top. Project wins on direct conflicts, but anything alpha said that beta didn’t explicitly contradict bleeds through. The persona mostly switches, but it’s contaminated.
This is the entire trap, in one paragraph.
Why this isn’t an obvious bug
You can spend an hour staring at your plist before suspecting os.homedir(), because:
printenv HOMEfrom inside the agent shows the override correctly.- Node’s
process.env.HOMEreports the override correctly. - Bash’s
$HOMEexpands to the override. - Tools like
git(which respectXDG_CONFIG_HOMEorHOME) behave correctly.
The behavior of os.homedir() looking past $HOME is documented in the Node docs (it falls back to getpwuid(getuid()) on Unix when the env var is unset, but on macOS it actually prefers the password database in some build configurations), but the behavior is surprising enough that most teams discover it through a debug session, not the docs.
Cross-language note: this trap is Node-specific. Python’s os.path.expanduser('~') and pathlib.Path.home() honor $HOME first and only fall back to pwd.getpwuid() when the env var is unset — so a Python-based agent runtime with HOME override would not hit the same leak. If you’re hand-rolling agent tooling around Claude Code (e.g., wrapper scripts that resolve ~), os.path.expanduser('~') in Python is the safer one-liner; os.homedir() in Node needs explicit process.env.HOME fallback to behave the same way. Claude Code itself is Node, so this post’s diagnosis applies regardless of what your wrapper layer is written in.
Even more confusingly: if you don’t have a global CLAUDE.md at all, the leak is silent and harmless. Beta just reads its project file and works. The bug only surfaces when you have any shared global content — which most multi-agent setups eventually do, because the global is exactly where shared protocol (Discord channel rules, security policy, communication conventions) wants to live.
The minimal fix
Two-part fix, both pieces required:
Part 1: Project-level CLAUDE.md per agent, with explicit persona.
~/agents/alpha/.claude/CLAUDE.md # alpha persona, full
~/agents/beta/.claude/CLAUDE.md # beta persona, full
Each project file should restate the persona explicitly (name, role, tone, sign-off pattern, what this agent does not do). Don’t rely on inheritance from global. Don’t write “I am beta” once and assume it carries through. Restate it.
Part 2: Keep the global CLAUDE.md agent-neutral.
~/.claude/CLAUDE.md # team protocol only — no persona, no name
The global file is shared by every agent that runs as the system user. Put protocol there: communication rules, channel conventions, security policy, hooks. Do not put any agent’s persona, name, or tone there, because every agent will read it as their own.
If there’s something only one agent needs (e.g., “as the implementation-focused agent, default to writing tests first”), it goes in that agent’s project CLAUDE.md, not the global.
After the fix, beta reads:
~/.claude/CLAUDE.md— protocol (no persona content)~/agents/beta/.claude/CLAUDE.md— beta’s persona, restated explicitly
The persona is loud and unambiguous. There’s nothing in the global that beta could absorb as its own identity.
Verification ritual
Don’t trust the fix until you’ve run the smoke test. Three steps, two minutes:
-
Persona check: Open a fresh session of beta. Send the prompt:
What is your name and role?. Beta should answer with beta’s identity, not alpha’s. Ask the same of alpha. Both should answer with their own. -
Tone divergence check: Send the same neutral prompt to both agents (e.g.,
Walk me through the steps to add a new column to our users table). The two responses should sound noticeably different — different word choice, different structure, different sign-off. If they sound similar, the global is leaking. Inspect the globalCLAUDE.mdfor any content that could be persona-relevant. -
Memory isolation check: Have alpha save a memory about a topic specific to its role. In beta’s next session, ask about that topic. Beta should not surface alpha’s memory automatically. (If your auto-memory uses the system user’s home — and it almost certainly does — both agents share a memory tree. You’ll need to scope memory by project-slug or set up agent-private memory dirs to fully isolate. But that’s a separate problem; for now, just verify that beta doesn’t accidentally recall alpha’s content as its own.)
If all three pass, the persona leak is closed.
Three follow-up mistakes that re-introduce the leak
Six months in, the persona leak comes back. It’s almost always one of these:
Mistake 1: Adding a “team intro” section to the global.
Someone writes: ”## Team — alpha handles frontend, beta handles backend, gamma handles ops”. Reasonable. Now every agent reads this and starts referring to itself by name and mentioning the others. Tone bleeds. Sign-offs get confused. Solution: keep team intro in each agent’s project file (“you work alongside alpha and gamma — they handle X and Y”), framed from that agent’s perspective. The global stays neutral.
Mistake 2: Adding examples to the global.
A team adds example code snippets or response templates to the global. The examples are written in one agent’s voice (because someone copy-pasted them from a session). Other agents start mimicking that voice. Solution: examples go in project files, or — if shared — get explicitly framed as “these are illustrative; match your own persona’s tone”.
Mistake 3: Using auto-memory across agents without scope.
By default, auto-memory lives at ~/.claude/projects/<cwd-slug>/memory/. Two agents with the same cwd-slug share memory. Two agents with different cwd get different slug paths but the system home is shared. If you don’t explicitly scope memory by agent identity, alpha’s saved feedback (“we use snake_case here”) gets read by beta, who applies it as if it were its own learned rule. Solution: set DISCORD_STATE_DIR and similar per-agent env vars, and either symlink each agent’s memory to a per-agent path or use the project-level CLAUDE.md to re-state identity-relevant rules (so the agent loads them as its own, not as memory it inherited from a sibling).
All three mistakes are easy to make because they look like sensible refactors. None of them get caught by tests. The only way to catch them is to re-run the verification ritual periodically — once a week, or whenever an agent’s tone starts drifting toward another’s.
Where the responsibility sits
This isn’t a bug in Claude Code. os.homedir() is doing exactly what its documentation says. The bug is in the multi-agent pattern, which assumes that HOME override is a complete identity boundary. It isn’t. HOME is a partial boundary that handles 80% of identity-resolution paths, and the remaining 20% — the path through os.homedir() — needs the project-level CLAUDE.md + slim global combo to seal.
If you’re running one agent per machine, you’ll never hit this. If you’re running multiple agents, you’ll hit it within an hour and it will look mysterious. Now you know the diagnosis and the fix.
Further reading
If you’re setting up a multi-agent Claude Code team for the first time, the Claude Agent Pack walks the full bring-up — runtime isolation (HOME + plist + tmux socket), persona + memory layer (this post is a deep dive on the persona half), peer communication patterns (Discord channel model + mention-required push), and lifecycle discipline (handoff, restart, stale-context detection). Tutorial 02 in the pack covers the persona + memory layer end-to-end and includes a smoke test you can run in five minutes.
If you’re debugging a leak and want to compare against a known-working setup, the pack also ships the templates/multi-agent-claude-code/ tree — copy-paste skeleton files for global vs project CLAUDE.md, with the explicit “what belongs where” annotations.