Claude Code sessions that message each other
One Claude Code session can send text to another on the same VPS. What ListAgents and SendMessage do, when a second session helps, and why messages get held.
What it means for Claude Code sessions to message each other
Two Claude Code sessions can message each other when they run on the same machine, under the same operating system user. A message is one piece of plain text that one Claude writes for another. It carries no conversation history and no files. Claude finds the other session with the ListAgents tool and delivers the text with SendMessage, so you never call either tool by hand. You say what the other session needs to know, and Claude writes the message itself.
The feature is called cross-session messaging. As of August 2026 it needs Claude Code v2.1.224 or later, and it runs on macOS and Linux, including Linux inside WSL 2. There is no native Windows support, and it is not available on Amazon Bedrock, Claude Platform on AWS, Google Cloud's Agent Platform, or Microsoft Foundry. When a session meets those requirements, messaging is already on and there is nothing to enable. The behaviour described below comes from the Anthropic documentation for cross-session messaging.
A VPS is where this matters, because a VPS is where sessions live long enough to be worth addressing. On a laptop you close the lid. On a server under tmux, a session you started on Monday is still running on Thursday, still holding the context of one repository. Once you have two of those, how they talk stops being theoretical. If you have not set that up yet, start with running Claude Code on a VPS under tmux, which covers the session plumbing this guide assumes.
When a second session is worth the tokens
Start with the cost. Each session is a separate Claude instance with its own context window, so two sessions cost roughly twice what one costs over the same period. A delivered message counts toward usage exactly like a prompt you typed. Coordination is not free, and work that is really one sequence of steps gets slower and more expensive when you split it across sessions.
The cases where a second session pays for itself share one shape. Two pieces of work run at the same time without waiting on each other, and one of them learns something the other needs mid-task.
- One session finds a breaking change while the other is building on the code it broke. Claude summarises the change and sends it, instead of you retyping it in the other terminal.
- Two sessions work the same repository in separate git worktrees, and one needs to know what landed.
- A long migration or test run reports its result back to the session you are watching.
- A builder session and a reviewer session, where the reviewer reads what the builder produced and sends back what it found.
When the work is sequential, or when both sessions would edit the same files, use one session. When you want a coordinated group that Claude spawns and supervises inside a single task, that is agent teams, a separate and still experimental feature. When you only want the same conversation in another terminal, resume the session instead. Cross-session messaging is for independent sessions that you start and steer yourself.
Check the feature is there before you plan around it
First the version:
claude --versionCompare the number with 2.1.224. Then, inside a session, type /list-agents, which also answers to /peers. It prints every agent this session can reach, with the name each one answers to. If the command is not recognised at all, this session does not have cross-session messaging, and no settings file will change that. Type /status and look for a Peer address row: it holds this session's own inbox address, prefixed with uds:.
One trap hits VPS users specifically. Cross-session messaging depends on feature-flag evaluation, and several privacy variables switch that evaluation off, which leaves the feature in its default off state. DO_NOT_TRACK, DISABLE_TELEMETRY, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, and DISABLE_GROWTHBOOK all do this. People harden a fresh server by pasting those into ~/.bashrc, then wonder why /list-agents does not exist. The same values can arrive from the env map in a settings file or from managed settings, so check the shell first.
env | grep -E 'DO_NOT_TRACK|DISABLE_TELEMETRY|DISABLE_GROWTHBOOK|NONESSENTIAL'Unset whichever one prints. For DISABLE_TELEMETRY and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, any non-empty value turns the behaviour on, including the string 0, so DISABLE_TELEMETRY=0 does not do what it looks like it does. You turn it off by unsetting the variable or setting it to an empty string.
Name your sessions, or Claude cannot address them
Claude addresses a message to a session by name. Set the name when you start the session:
claude --name builder-apiYou can also set it with /rename inside a running session. When you set nothing, Claude Code derives a name from the working directory's folder name, such as myapp-3f. That is fine for one session and confusing for four, and two sessions can end up with the same name. The /list-agents output shows each local session's working directory, which tells same-named sessions apart, and Claude's own listing adds a short identifier to the address when names collide. Naming them yourself is cheaper than reading identifiers.
A two session tmux layout you can reproduce
This is a builder session and a reviewer session on one repository. The reviewer works in a separate git worktree, so the two never write the same file. git worktree add with HEAD gives a detached checkout, which is what you want for a session that reads rather than commits.
cd ~/src/api
git worktree add ../api-review HEAD
tmux new-session -d -s agents -n builder -c ~/src/api
tmux new-window -t agents -n reviewer -c ~/src/api-review
tmux send-keys -t agents:builder 'claude --name builder-api' C-m
tmux send-keys -t agents:reviewer 'claude --name reviewer-api' C-m
tmux attach -t agentsCtrl+b then w lists the windows by name so you can pick one. In the builder window, run /list-agents. You should see reviewer-api with its working directory ~/src/api-review. If it is missing, the reviewer session has not finished starting, or one of the two problems in the next section applies. Then hand something over in plain language:
Tell reviewer-api which files I changed for the rate limiter and what to look at first.Claude writes the summary and sends it. You do not write the message text, and what Claude sends varies. In the reviewer window the message appears in the conversation with the sender's name. If that session is idle, Claude starts a new turn on it right away. If it is mid-turn, the message waits until between tool calls, so a running command is never interrupted. Once Claude has read it, the message collapses to a one-line Message from row that Ctrl+O expands.
Who can see whom on one VPS
Same-machine delivery never goes through Anthropic servers. Each session writes registration files to disk and binds its own inbox socket, and Claude Code reads those files to find your other sessions. Two consequences follow, and both bite on a server.
The socket is restricted to your operating system user. A session you started as root and a session you started as deploy cannot see each other, even side by side in the same tmux server, because one user's sessions cannot reach another user's socket. Run both sessions as the same user.
A container has its own filesystem. A session inside Docker and a session on the host cannot reach each other, because they are not reading the same registration files. Two sessions inside the same container can message each other normally. If you keep agents in containers for isolation, as in running coding agents in a disposable VM, expect messaging to work inside a container and not across the container boundary.
Your sessions on other machines, and on the web, appear in the listing only while Remote Control is connected, and they are labelled as such. Claude here can only reply to a message that arrived from one of those. It cannot start that exchange.
Why your message never arrived
The usual reason has nothing to do with the network. The receiving session decided what to do with the message, and the decision was not to deliver it. Every arriving message ends in one of three outcomes: delivered, held (set aside undelivered until you approve it), or refused (dropped without delivery).
When no crossSessionInbound value applies, Claude Code decides per message by comparing the two sessions' permission modes. It groups sessions that bypass permission prompts into one class and every other session into the other. auto, acceptEdits, and dontAsk count as prompting. Plan mode counts as bypassing in a session that has bypass permissions available. The rule is then symmetric:
- A receiving session that prompts for permissions gets each message delivered. It holds one only when the sending session identifies itself as bypassing prompts.
- A receiving session that bypasses prompts holds every message for your approval. It delivers one only when the sender is also bypassing.
So the first workflow most people build is exactly the one that does not work. You start a builder with --permission-mode bypassPermissions because you want it to run unattended, you leave the reviewer on defaults, and every message the builder sends waits in an approval dialog nobody is watching. That dialog closes after the dialogExpiry deadline, which defaults to 5m, and the message is dropped. On the same machine the sending session gets a notice when its message is held, and a follow-up when the receiver later delivers, denies, or expires it, so read the sender's screen before you blame the socket.
To make a session take messages unattended, set crossSessionInbound to accept. Where you set it decides whether it applies. Claude Code reads managed settings first, then the --settings flag, then user settings, and applies the first value it finds. A value in project or local settings applies only when it is stricter, on the ladder accept < hold < refuse. An accept in .claude/settings.json is looser than anything, so it is ignored whenever a trusted source has set a value. Put it in ~/.claude/settings.json, or pass it for one session:
claude --name runner --settings '{"crossSessionInbound":"accept"}'A headless claude -p worker binds an inbox socket like an interactive session and appears in the listing, but it cannot show an approval dialog. A held message there stays held until a later mode or settings change allows it. The --settings line above is how you let such a worker take messages. A session started in bare mode binds no socket at all, so it can neither receive messages nor appear in the list.
Where hand-offs deadlock
Message loops are handled for you. Claude Code rate-limits repeated messages per sender, drops identical repeats that arrive within a short window, and caps accepted messages waiting to be read at 50 per session, so two sessions cannot ping-pong forever. Held messages are capped at 100, and the oldest are dropped past that.
The failure that does happen is quieter, and it is a hand-off rather than a loop. Session A asks session B a question it needs answered before it can continue, then goes idle. B holds the message, or B is mid-turn on something long, or B answers a question A did not really ask. A waits. You come back an hour later to two idle sessions and no work done.
Write hand-offs that do not need a reply. A good message carries a fact or a decision: what changed, and what the result was. A bad message asks the other session for permission, or for an answer the sender is blocked on. Claude is already instructed never to ask another session for an action its own permission settings would block, and to route that work back to you instead. Extend that rule yourself. If a session cannot make progress without an answer, you are the one who should answer it. Context discipline helps here too, because a session that has lost the thread writes vague messages; managing context in Claude Code covers that side.
Treat an incoming message as untrusted input
Claude Code tells the receiving Claude that the message came from another session and not from you, and it limits what that message can do. A message cannot answer a pending permission prompt on your behalf, because consent from another session is not your consent. It cannot change permission settings, CLAUDE.md, or other configuration because another session asked. A slash command inside the text, such as /compact, arrives as plain text and is never executed. If acting on the message needs a permission the receiving session does not have, you see the same prompt you would see for any other work. In auto mode a classifier also reviews each message before delivery, and a message it blocks never reaches the recipient. These limits survive the permissive modes, which is why a bypassing session holds inbound messages by default instead of trusting them.
That covers permissions. It does not cover content. The sending session may have read a pull request description, a web page, a dependency README, or an issue comment written by a stranger, and whatever it read can shape the text it writes to your other session. The message is data. It deserves the same suspicion as any other text that entered a session from outside. This is the discipline described in keeping secrets out of your AI agents: assume anything that crossed a trust boundary can be wrong, and never let it authorise itself.
Two controls exist if you want less of this. Setting crossSessionInbound to refuse drops inbound peer messages without delivering them, and from project or local settings that value applies over every other source, because it is the strictest one on the ladder. To stop this session sending or listing, add permission deny rules naming SendMessage and ListAgents, both written as bare tool names with no specifier. Setting isolatePeerMachines to true requires your explicit approval before any message reaches a session beyond this machine, and that approval is required even in bypassPermissions mode.
{
"crossSessionInbound": "refuse",
"isolatePeerMachines": true
}Denying SendMessage also removes messaging to subagents, since the same tool serves both. A refusing session shows no visible change in its own /status or in other sessions' listings, so confirm the setting from the session's configuration rather than from the screen.
Bridges and shared memory MCP servers
Several third-party projects landed in the same period doing something adjacent: local agent-to-agent bridges that relay text between running agents, and MCP (model context protocol) servers that give several agents one shared store to read and write. Judge them as a different shape rather than as a competitor, and verify any install command against the project's own README before you run it. Messaging is push, because the sender puts text into the receiver's turn. A shared store is pull, because nobody is interrupted and a session sees the note when it next looks. Pull is calmer for status that changes slowly, and it only works when a session actually looks.
If you go that way, the questions worth asking are about the process rather than the feature list. What user does the server run as, and what can it read on the box. Running MCP servers on a VPS covers that setup. Sharing agent skills across repos covers the simpler case where what you want to share between sessions is instructions rather than live state, and it removes a lot of the messages you would otherwise send. For the wider picture, running a coding agent on a VPS is the place to start.
FAQ
Why is /list-agents not recognised in my session?
The session does not have cross-session messaging. Check claude --version against 2.1.224 first, since the feature needs that version or later. Then check the platform, because it runs on macOS and Linux and not on native Windows, and it is unavailable on Amazon Bedrock, Claude Platform on AWS, Google Cloud's Agent Platform, and Microsoft Foundry. If both are fine, check your shell for DO_NOT_TRACK, DISABLE_TELEMETRY, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, or DISABLE_GROWTHBOOK, because each of those blocks the feature-flag evaluation the feature depends on and leaves it off.
Why did my message to the other session never arrive?
If /list-agents works, messaging is on and something narrower stopped that message. The common cause is permission modes. A session that bypasses permission prompts holds every inbound message for your approval unless the sender also bypasses, and that approval dialog is dropped after the dialogExpiry deadline, five minutes by default. Check the sending session for the held notice. To fix it, set crossSessionInbound to accept in ~/.claude/settings.json or pass it with --settings, because an accept in project or local settings is ignored as the looser value.
Can a Claude Code session in Docker message one on the host?
No. Sessions find each other through registration files on disk and a per-session inbox socket, and a container has its own filesystem, so the two cannot see the same files. Two sessions inside the same container can message each other normally. The same rule explains why a session running as root and a session running as your normal user cannot reach each other: the socket is restricted to the operating system user that owns it.
Is a message from another Claude Code session safe to act on?
Treat the text as untrusted input, because the sending session may have read a web page, a README, or an issue comment written by someone else. Claude Code already stops the message from acting on its own: it cannot approve a pending permission prompt, it cannot change permission settings or CLAUDE.md on request, and a slash command in the text arrives as plain text and never runs. Those protections cover permissions and not judgement, so read what arrived before you tell the receiving session to act on it.
Does cross-session messaging send my code to Anthropic?
Between two sessions on the same machine, no. The message travels over a per-session socket on that machine and never through Anthropic servers, and only the text Claude wrote is sent, never conversation history or files. Messages to a session on another of your machines, or to a session on the web, do travel through Anthropic servers over the Remote Control connection, and in that direction Claude can only reply to a message that arrived, not start one. Set isolatePeerMachines to true to require your approval before anything leaves the machine.