Claude Code Cross-Session Messaging: Sessions That Talk

Plain text only. A message is a piece of text one Claude writes to another — never conversation history and never files. The receiving session gets the sender's name, the text, and a reply address. If you need the whole context in another terminal, resume the session instead of messaging it.
No. Claude Code tells the receiving Claude that the message came from another session rather than from you, so it never counts as consent and cannot answer a pending permission prompt. The receiving Claude is also instructed never to change permission settings or CLAUDE.md because another session asked, and slash commands inside message text arrive as plain text.
Only when they leave the machine. A message to a session on the same machine travels over a per-session Unix socket, or a named pipe on native Windows, and never touches Anthropic servers. Messages to your sessions on other machines ride the Remote Control connection, and messages to Claude Code on the web go straight to the cloud session.
Same-machine delivery works by each session registering itself in files on disk, so two sessions can only reach each other when they see the same files. A container has its own filesystem, so a session inside it and one on the host are isolated. The same applies to a WSL 2 session and a native Windows session on one computer.
Receiving and sending are separate controls. Set crossSessionInbound to refuse to drop inbound messages, and add permission deny rules naming SendMessage and ListAgents to stop this session sending or listing. Administrators can combine both in managed settings to turn the feature off across an organisation.

Key Takeaway
Claude Code cross-session messaging lets one of your sessions send a plain-text message to another by name, using the ListAgents and SendMessage tools. Never conversation history and never files — just text. Same-machine delivery goes over a per-session socket that never touches Anthropic servers; cross-machine delivery rides the Remote Control connection.
I had three terminals open on the same repository: one migrating a schema, one rewriting the API layer against it, and one I was actually watching. The API session spent eleven minutes building against a column that had been renamed forty minutes earlier in the migration session. I was the integration layer between them, and I was asleep at it.
Cross-session messaging is the fix for exactly that, and it is on by default on a recent enough version with nothing to enable. This post covers how sessions address each other, the three outcomes an arriving message can have, the trust model that stops a message being used as consent, where a message physically travels, and the socket underneath it that a hook can post into.
The unit here is deliberately small: one Claude writes a piece of text to another Claude. It is never conversation history and never files. If what you want is the whole context, that is not messaging, that is resuming the session — and reaching for the wrong one of those is the most common mistake with this feature.
Claude uses two tools, and you call neither yourself. ListAgents discovers what it can reach and SendMessage delivers to one target by name. The same SendMessage tool also addresses subagents and agent-team teammates, which matters later: denying the tool removes all three at once.
You can leave the targeting entirely to Claude, or name the session yourself with an at-mention. The mention picker only appears once you type at least one letter after the at-sign — after a bare at-sign, session rows do not appear at all, which reads as the feature being missing.
# See what this session can reach. The FIRST line is this
# session's own name — the one your other sessions use to
# reach it. This session is never one of the rows below it.
/list-agents # also available as /peers
# Address a session by name in your own prompt. Type @ then
# at least one letter; a bare @ shows no session rows.
Let @api-worker know the schema migration finished
# Or leave the targeting to Claude entirely:
Ask the session in my other terminal whether the migration finished
# Ask to be told once when another session goes idle.
# One-shot, no polling, dropped after 12 hours.
Tell me when the migration session finishes what it is working onNames are not guaranteed unique. When you start or rename a session with a name another live session already holds, Claude Code leaves the name with the session that has it and renames yours to a variant. Sessions can still end up sharing a name, so the listing shows each local session's working directory to tell them apart, and when more than one live session answers to a mentioned name Claude asks you which you meant before sending.
The receiving Claude reads a message between tool calls during an active turn, so a running tool is never interrupted; if the session is idle, the message starts a new turn. What most people miss is that arrival is not binary. Every message ends in one of three states:
This is the part worth reading twice, because it is what makes the feature safe to leave on. When session A messages session B, Claude Code tells B's Claude the message came from another session rather than from you, and constrains what it can do:
The rule extends to the sending side too: Claude is instructed never to ask another session to do something that was denied or blocked in its own session, and to route that work back to you instead. A teammate that was refused an action cannot relay it to a peer to get it done. That closes the obvious loophole, and it is the reason I stopped worrying about leaving inbound on accept.
One setting decides what a session does with inbound messages, and it has a genuinely useful middle value. There is also a separate control for messages leaving the machine, and a pair of deny rules for turning sending off entirely.
// Choose what a session does with messages from your others.
{
"crossSessionInbound": "accept" // accept | hold | refuse
}
// Require your approval before any message leaves this machine.
// A true from ANY settings scope applies, so a checked-in
// project file can turn the requirement ON but never OFF.
{
"isolatePeerMachines": true
}
// Turn it off for a whole organisation, in managed settings.
// Note both directions: deny rules stop sending and listing,
// crossSessionInbound stops receiving.
{
"permissions": { "deny": ["SendMessage", "ListAgents"] },
"crossSessionInbound": "refuse"
}When no value applies, Claude Code decides per message from the two sessions' permission-mode classes. Sessions that bypass permission prompts form one class and everything else the other. A session that prompts for permissions delivers each message, holding one only when the sender identifies as bypassing; a session that bypasses holds each message for your approval, delivering only when the sender also bypasses. A held message opens an approval dialog that expires after five minutes by default and can be set to never expire.
Whether a message touches Anthropic servers depends entirely on where the other session runs, and that distinction is worth knowing before you send anything sensitive between terminals.
Three destinations, three routes:
| Where the other session runs | How it travels | Touches Anthropic servers |
|---|---|---|
| On this machine | Per-session Unix socket, or a named pipe on Windows | No |
| On another of your machines | Over that machine's Remote Control connection | Yes |
| On Claude Code on the web | Straight to the cloud session | Yes |
Same-machine delivery works by each session registering itself in files on disk, so two sessions can reach each other only when they can see the same files. A session inside a container and a session on the host cannot message each other. Neither can a session inside WSL 2 and a native Windows session on the same computer, because they register under different home directories and listen on different socket types. This looks like a bug and is a filesystem boundary.
Each session with messaging binds an inbox socket, and Claude Code exports both its path and a per-session token to hooks and Bash commands. That turns a private implementation detail into something genuinely useful: a script or hook can post back into its own session, which is how you get a long build or a CI callback to speak into the conversation that started it.
# Each session binds its own inbox. Find the path two ways:
/status # the "Peer address" row, prefixed uds:
# Or from a hook or Bash command, where Claude Code exports it:
echo "$CLAUDE_CODE_MESSAGING_SOCKET"
echo "$CLAUDE_CODE_MESSAGING_TOKEN"
# A script can post back into its OWN session's socket. On
# native Windows the auth line is REQUIRED; on macOS and Linux
# it is optional. Send it as the first line of the connection:
#
# type=auth, token=$CLAUDE_CODE_MESSAGING_TOKEN
#
# Unix domain socket on macOS/Linux, named pipe on Windows.
# Restricted to your OS user either way, so another user's
# sessions on a shared machine cannot deliver to it.Own-child messages get special treatment. When no inbound value applies, Claude Code delivers a message it can verify came from the session's own child processes. On Linux it can verify by process evidence even after the child exits; on macOS only while the process is still running; on native Windows and in containers where Claude Code is process ID 1, the exported token in the auth line is the only proof available. If you are scripting this, send the auth line regardless — it is optional on Unix and required on Windows.
Two sessions that can each write to the other are a loop waiting to happen, and the channel is designed on that assumption:
The habit that made this worth setting up was narrow: name every session I intend to keep, so I can address it later without a listing step. After that, the useful pattern is one long-running session doing something slow and one session I am actually watching, with a request for a single idle notice between them. It replaced the thing I was doing badly by hand, which was remembering to check.
Sources & further reading