关于 claude-remote-peers
claude-remote-peers 通过 SSH 转发 Unix socket,让不同机器上的 Claude Code 会话互相发送消息。它不复制注册记录或密钥,也不会在远端启动 Claude 进程。
主要功能
- 01SSH 转发会话收件箱 socket
- 02通过地址直接发送消息
- 03回复主动推送到本地会话
- 04检查转发状态并清理 socket
- 05支持指定远程进程与 SSH 参数
适用场景
- 01让两台开发机器上的 Claude Code 会话通信
- 02将消息发送到特定远程会话
- 03完成跨机器协作后清理转发
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vivswan/ skills --skill claude-remote-peers安装后在 Claude Code 中输入/claude-remote-peers确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSE.mdREADME.md.codex-plugin/plugin.jsonagents/openai.yamlreferences/protocol.mdscripts/bridge.mts详细介绍
Claude Remote Peers
两台不同机器上的 Claude Code 会话可以像同一台机器上的两个会话那样互相发送消息。向 Unix 套接字写入内容会唤醒另一端。桥接程序会通过 ssh 将每个会话的收件箱套接字转发到另一台机器上的相同路径。不会复制任何内容,也不会轮询。
示例
两台开发机器,每台各运行一个 Claude Code 会话。在 A 机器上运行 ListAgents 时,没有显示任何对等会话。桥接程序从 A 机器会话的 shell 中运行:
$ bun "<skill-dir>/scripts/bridge.mts" box-b
bridge up: session-b (pid 2002 on box-b), bridge pid 4242
SendMessage to: uds:/tmp/cc-socks-1000/2002.sock
they reply to: uds:/tmp/cc-socks-1000/1001.sock
使用 to: uds:/tmp/cc-socks-1000/2002.sock 的 SendMessage 成功发送到 B 机器。B 机器上的会话回复了消息中的 from 地址。回复自动送达 A 机器上的当前轮次:
<cross-session-message from="uds:/tmp/cc-socks-1000/2002.sock" from-name="session-b" from-mode="bypass">
bridge ok
</cross-session-message>
两台机器的 ~/.claude 中都没有写入文件。
权限
是否向另一台机器上的会话发送消息由用户决定,每个会话只需询问一次。向本机上的会话发送消息则无需询问。
首次向远程地址发送 SendMessage 之前,请询问用户,并说明主机和会话(box-b、session-b、pid 2002)。用户同意后,之后向该对等会话发送的所有消息都无需再次询问。
用户的指示本身就是授权,授权范围由指示中提到的对象决定。“与 box-b 上的 Agent 交谈”涵盖 box-b 上的所有会话。“与 box-b 上的 session-b 交谈”仅涵盖该会话,无需另行询问。
任何超出授权范围的情况都需要重新询问:例如另一台主机,或用户按会话授权的主机上的另一个会话。
将授权记录在本会话自己的笔记中(任务列表或计划),不要记录在记忆中。新会话需要重新询问。用户表示撤销授权时,删除该笔记。
工作流
-
在后台启动桥接程序,从本会话内部的 shell 启动(其 Bash 工具会继承
CLAUDE_CODE_MESSAGING_SOCKET)。<destination>可以是ssh接受的任何内容。nohup bun "<skill-dir>/scripts/bridge.mts" <destination> > /tmp/claude-remote-peers.log 2>&1 < /dev/null & disown
指定远程 pid,或在 -- 后添加额外的 ssh 选项:
bun "<skill-dir>/scripts/bridge.mts" box-b 2002
bun "<skill-dir>/scripts/bridge.mts" [email protected] -- -p 2222 -i ~/.ssh/other_key
--help:
usage: bridge.mts <ssh-destination> [remote-pid] [-- <extra ssh options>]
run from a shell inside a Claude Code session (it sets CLAUDE_CODE_MESSAGING_SOCKET)
prints: SendMessage to: uds:<remote socket> once both forwards are confirmed; stays up until ssh exits or a signal
exits: 0 clean shutdown (a signal, or ssh exited 0); 1 a setup, tunnel, or cleanup failure
-
等待日志中的
SendMessage to:行。只有在脚本确认两个转发都已建立(包括通过 ssh 建立的远程转发)之后,才会打印该行。更早出现的套接字文件尚不能证明连接可用。 -
将打印出的地址用作
to发送消息。 远程端会收到普通的<cross-session-message>。它会回复from地址,无需你做任何操作。 -
回复会主动推送。 回复会以
<cross-session-message>的形式送达,并在此处开启一个轮次。切勿轮询日志、套接字或远程注册表来检查回复。 -
完成后停止桥接程序,使用
bridge up行中的bridge pid。拆除时会移除两个转发的套接字,并记录cleanup done。如果远程移除失败,会记录错误并以状态码 1 退出。kill 4242
不使用 bun
也可以在 shell 中完成。远程 pid 和套接字路径来自远程注册表。CLAUDE_CODE_MESSAGING_SOCKET 是本会话自己的收件箱。
两项检查都会在任何内容被转发之前终止该函数。任何一端已存在的路径若是正在使用的收件箱,都不会被转发或移除。套接字路径中包含引号时,需要使用脚本,因为脚本会为远程 shell 正确处理引号。
h() { ssh -o ClearAllForwardings=yes box-b "$@"; } # helper commands drop config forwards that would collide with the tunnel's
h 'cat ~/.claude/sessions/*.json' # pick a live one; note its messagingSocketPath
remote_sock=/tmp/cc-socks-1000/2002.sock # that messagingSocketPath, e.g. this shape (uid dir, pid file)
bridge() {
unset bridge_pid # the cleanup below acts only on a forward this call started
mkdir -p -m 700 "${remote_sock%/*}" # their socket dir, needed here when the uids differ
test ! -e "$remote_sock" || { echo "a local session owns that path; stop"; return 1; }
h "mkdir -p -m 700 '${CLAUDE_CODE_MESSAGING_SOCKET%/*}' && test -w '${CLAUDE_CODE_MESSAGING_SOCKET%/*}' && test ! -e '$CLAUDE_CODE_MESSAGING_SOCKET'" \
|| { echo "our path on box-b is taken or its directory is not writable, or ssh failed; stop"; return 1; }
ssh -o ControlPath=none -o ExitOnForwardFailure=yes -N \
-L "$remote_sock:$remote_sock" \
-R "$CLAUDE_CODE_MESSAGING_SOCKET:$CLAUDE_CODE_MESSAGING_SOCKET" box-b & # no connection sharing: the forwards must die with this pid
bridge_pid=$!
}
bridge && sleep 2 && kill -0 "$bridge_pid" && h "test -S '$CLAUDE_CODE_MESSAGING_SOCKET'" && echo "bridge up" # a refused forward makes ssh exit; replies need the -R socket on box-b
# SendMessage to = uds:<the remote_sock value>
# afterwards; bridge_pid is set only by a bridge() call that started the forward
[ -n "$bridge_pid" ] && { kill "$bridge_pid"; rm "$remote_sock"; unset bridge_pid; h "rm -f '$CLAUDE_CODE_MESSAGING_SOCKET'" || echo "remove $CLAUDE_CODE_MESSAGING_SOCKET on box-b by hand"; }
桥接程序不会做什么
- 它不会在
ListAgents中列出远程会话。 列表只读取本地注册表。请使用脚本打印的uds:路径访问远程会话。 - 它不会复制注册表记录或密钥文件。 了解这种做法为何可行,以及复制为何更糟:寻址与认证和桥接程序为何不复制任何内容。
- 它不会在远程端留下任何运行中的进程,除了用于维持转发的 ssh 会话。查找、预检查和清理都通过短暂运行后退出的 ssh 命令完成。不会启动、恢复或派生任何 Claude 进程。
常见问题
| 你看到的内容 | 含义 | 操作 |
|---|---|---|
no live Claude Code session with a messaging socket on box-b | 另一端没有会话,或者其套接字已失效 | 启动或检查远程会话,然后重新运行 |
could not read the session registry on box-b | ssh 失败、远程端缺少 python3,或者无法读取 ~/.claude/sessions | 运行 ssh box-b python3 --version;上一行会显示远程错误 |
ssh to box-b failed (exit 255) | 空路径检查未能连接到远程端 | 先修复 ssh:ssh -v box-b true |
<path> already exists on this machine 或 on <destination> | 转发会话的这一端或另一端有一个会话的 pid 与其相同,或者遗留了过期的转发 | 重启一个会话或移除过期套接字,然后重新运行 |
ssh exited (255) before the forwards came up | 隧道在启动期间断开;ssh 自身的错误显示在上一行(转发被拒绝、登录失败) | 修复 ssh 报告的问题,然后重新运行 |
forwarded socket /tmp/cc-socks-1000/2002.sock never appeared within 60 s | ssh 保持运行,但始终未在此处创建 -L 套接字 | 检查 ssh -v box-b,确认两端都运行 OpenSSH 6.7+ |
reverse forward did not bind /tmp/cc-socks-1000/1001.sock on box-b within 60 s | ssh 保持运行(如果 -R 被拒绝,ssh 会退出),但套接字始终未出现在 box-b 的该路径下:其 sshd 会话看到的是不同的 /tmp(PrivateTmp、容器),或者绑定仍在等待中 | 在 ssh box-b 的 shell 中运行 ls -ld /tmp/cc-socks-<uid>;将 CLAUDE_CODE_TMPDIR 指向双方都能访问的目录 |
could not verify the reverse forward | 隧道建立后,探测自身的 ssh 连接失败;桥接程序已拆除隧道 | 先修复 ssh:ssh -v box-b true,然后重新运行 |
could not create a writable /tmp/cc-socks-<uid> on box-b | 远程 /tmp 拒绝创建该目录,或者该目录已存在且归其他账户所有(由 root 会话遗留) | 在远程端检查 ls -ld /tmp/cc-socks-<uid>;更改其所有者,或将 CLAUDE_CODE_TMPDIR 指向双方的其他目录 |
Timed out sending to /tmp/cc-socks-1000/2002.sock from SendMessage | 远程会话已退出;转发已无处可达 | 停止并重新运行桥接程序;它会选择最新的活动会话 |
[Cross-session delivery notice] ... held | 权限模式不匹配;远程用户必须批准 | 等待,或请对方将 crossSessionInbound 设为 accept |
两台主机上的 uid 不同,会生成不同的套接字目录(/tmp/cc-socks-<uid>)。脚本会在两端分别创建缺失的目录。Windows 接收端需要身份验证令牌,因此无复制设计在 Windows 上不适用。
关于上述内容的协议细节和示意图,见 references/protocol.md。