Running multiple Claude Code agents on one Mac: what actually breaks
Running a second Claude Code process on your machine is “just start another one”. Running two that behave like different agents — different personas, different memory, different Discord bot presence — is where the failure modes live.
This post walks through the exact setup that got three concurrent Claude Code agents running on one Mac Studio as peer Discord bots, coordinating without stepping on each other. Every design choice below is a reaction to a real failure. No speculation.
The three things that go wrong
- Identity bleed. Agent B reads
~/.claude/CLAUDE.mdand the same memory tree as Agent A. It ends up speaking in Agent A’s voice. tmuxenvironment leak. Both agents’plistfiles set differentHOMEvalues. Agent B’sHOMEoverride is silently dropped at thetmuxserver boundary — and Agent B starts running with Agent A’sHOME.- Discord bot silence. The
claude-plugins-official/discordplugin drops all bot-authored messages by default. Agents can’t see each other’s pings.
Each has a simple fix once you know where to look.
Fix 1: HOME override + project CLAUDE.md
Each agent gets its own HOME:
mkdir -p ~/agents/alpha/.claude
~/agents/alpha/.claude/CLAUDE.md holds the agent’s persona, role, and operating rules. When claude runs with HOME=~/agents/alpha, this file is picked up as a project-level CLAUDE.md (because the cwd is ~/agents/alpha) and stacks on top of the system user’s global file — project wins on conflict.
The subtle trap: the global CLAUDE.md still loads, because Claude Code resolves ~ via os.homedir() / getpwuid(), which reads the system user’s home, not your HOME env. Keep the global minimal or agent-neutral.
Same trap applies to auto-memory: it writes to ~/.claude/projects/<cwd-slug>/memory/, where ~ is the system user’s home. Copy any seed memory into that path.
Fix 2: tmux -L <socket> socket isolation
If your plist runs tmux new-session ... without -L, it connects to the default socket. If Agent A’s tmux server already owns that socket, Agent B’s new-session attaches to A’s server and inherits A’s env. Your HOME=~/agents/beta in Agent B’s plist makes it to the tmux client process and then gets dropped at the server boundary.
Symptom (after you notice something’s off):
ps ewwp <AGENT_B_PID> | tr ' ' '\n' | grep -E 'HOME|XPC_SERVICE_NAME'
# HOME=/Users/alice # ← should be ~/agents/beta, but it's the system user
# XPC_SERVICE_NAME=com.example.alpha-claude # ← spawned inside A's launchd service!
Fix: force a per-agent socket.
<string>exec /opt/homebrew/bin/tmux -L beta new-session -d -s beta-claude "...claude..."</string>
And pair -L <socket> on every operational command against that agent: tmux -L beta kill-session, tmux -L beta send-keys, tmux -L beta attach.
Why -L works: it tells tmux to use a distinct socket path, forcing a fresh tmux server per agent. No shared state, no inherited env.
Fix 3: Agent-agnostic Discord plugin patch
The default Discord plugin handler:
client.on('messageCreate', msg => {
if (msg.author.bot) return // drops every peer agent's messages
handleInbound(msg).catch(...)
})
Replace with a selective pass-through:
client.on('messageCreate', msg => {
if (msg.author.bot) {
const selfId = client.user?.id
const teamBotIds = ['<agent-a-id>', '<agent-b-id>']
const isTeamBot = !!selfId && teamBotIds.includes(msg.author.id) && msg.author.id !== selfId
const mentionsUs = !!selfId && msg.content.includes(`<@${selfId}>`)
if (!(isTeamBot && mentionsUs)) return
}
handleInbound(msg).catch(err => process.stderr.write(`discord: ${err}\n`))
})
selfId is resolved at runtime from client.user?.id — no per-agent hardcode. The patch is agent-agnostic.
One footgun: each agent runs with a different HOME, so each has its own plugin clone at <AGENT_HOME>/.claude/plugins/cache/.... Patch every agent’s copy, not just one.
The OAuth paste-bug workaround
Claude Code’s OAuth code-paste flow has a known bug inside tmux pseudo-TTY: pasting the code, then hitting Enter, doesn’t submit. Works fine for short dummy codes; hangs on the real 90-character one. Verified on CLI v2.1.119.
Path around it: do the first-time OAuth outside tmux.
tmux -L beta kill-session -t beta-claude 2>/dev/null
HOME=~/agents/beta /path/to/claude
# walk the wizard, paste via native Cmd+V, Enter
# "Welcome back" = auth persisted to Keychain + ~/agents/beta/.claude/
/quit
# now start the real, tmux-wrapped agent
launchctl kickstart gui/$(id -u)/com.example.beta-claude
Once Keychain has the token, every subsequent tmux-wrapped start skips the wizard entirely. The paste bug only matters on that one bootstrap.
Does this complexity pay off?
For a single-developer workflow, running two or three agents rather than one makes sense when:
- The agents have genuinely different roles (e.g., one owns infrastructure, another owns code review). Role specialization surfaces more problems earlier.
- You want separate Discord bots so you can mention each independently.
- You’re OK spending ~30 minutes on the core fix the first time (socket + plist + plugin-patch); plan on roughly an hour total when you also walk the full tutorial (auth bootstrap, OS-level verify, Discord wiring).
If you just want “more throughput”, a single agent with bigger context is easier. The multi-agent setup earns its keep when different personas reviewing each other’s work is what you actually want.
Where to get the full template
We published the complete launchd plist, shell wrappers, discord plugin patch, OAuth bootstrap helper, and step-by-step tutorial as part of the Claude Agent Pack on Gumroad. If you’d rather copy-paste and skip the discovery phase — figure roughly an hour of focused work end-to-end against the tutorial — that’s the path.
The Gumroad listing goes live shortly. This post covers the same ground in outline; the pack is the version with every placeholder filled in.
The tutorial walks the same ground this post covers, but with every placeholder filled in, every command exactly runnable, and a pitfalls table you can cross-reference when something breaks. See tutorials/01_multi_agent_setup_quickstart.md for the runtime isolation layer, 02_agent_persona_and_memory.md for personas and memory, 03_peer_communication_patterns.md for Discord coordination, 04_lifecycle_and_handoff.md for session hygiene, and 05_templates_index.md for the copy-paste starter files keyed off templates/multi-agent-claude-code/.
If you hit a failure mode this post didn’t cover, the issue tracker on the pack repo is the right place — we update the tutorial when new edge cases surface.