Remote SSH: plugin sync starves the RPC heartbeat, connection is force-closed mid-sync and restarts from zero — session never leaves "Setting up plugins..."

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 12, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

On a high-latency SSH link, RemotePluginSync and the RPC heartbeat share one channel. The plugin uploads starve server.ping, the client declares a healthy transport dead, reconnects, and restarts the plugin sync from zero. The session never leaves "Setting up plugins...".

Two defects combine, and either alone would be survivable:

1. Plugin uploads starve the health check on the same channel. server.ping cannot get a reply while a multi-MB tarball occupies the transport, so it times out and the client force-closes a connection that is fine.

2. A killed sync round discards its completed work. After reconnecting, the same plugin is uploaded again to a different hash directory. Nothing is reused, so progress never accumulates across rounds.

The consequence is a hard ceiling, not a slowdown: the entire plugin set must fit inside one heartbeat window or the session can never start. On a ~220 ms link that window fits about 5-6 plugins. With 11 enabled it is structurally impossible, and the UI reports nothing but a spinner.

This is distinct from #76447 (archives arriving truncated) — here extraction succeeds every time and is logged as such. It is distinct from #25664, which is a separate bug I hit alongside this one and have commented on there.

What Should Happen?

Any one of these breaks the loop:

  • Send the heartbeat on a separate channel, or suspend it while a transfer holds the transport.
  • Reuse already-synced plugin directories across reconnects, keyed on content, so a retry resumes instead of restarting.
  • Surface the failure in the UI. "Setting up plugins..." forever, with a clean-looking remote-server.log, gives the user nothing to act on.

Error Messages/Logs

Client ssh.log. Five plugins sync in 17 s, then the heartbeat kills the transport and the round is lost:

18:10:29 [warn]  [RemotePluginSync] Skipping .../claude-md-management/1.0.0 — upload failed: No response from server
18:10:34 [info]  [RemotePluginSync] Synced 120 file(s) from .../code-review/72d4573c1f65 -> /home/<user>/.claude/remote/plugins/f2eff3797d9747b6 (757ms extract)
18:10:38 [info]  [RemotePluginSync] Synced 122 file(s) from .../code-simplifier/1.0.0 -> /home/<user>/.claude/remote/plugins/eec1b3cd6c8a89af (432ms extract)
18:10:42 [info]  [RemotePluginSync] Synced 118 file(s) from .../context7/72d4573c1f65 -> /home/<user>/.claude/remote/plugins/dd1491335d8bb43f (480ms extract)
18:10:46 [info]  [RemotePluginSync] Synced 121 file(s) from .../frontend-design/72d4573c1f65 -> /home/<user>/.claude/remote/plugins/470cf81c349eee60 (430ms extract)
18:10:51 [info]  [RemotePluginSync] Synced 118 file(s) from .../playwright/72d4573c1f65 -> /home/<user>/.claude/remote/plugins/d6c4f934ea222ca2 (706ms extract)
18:10:57 [warn]  [RemoteRPCClient] heartbeat miss 1/2: RPC call to server.ping timed out
18:11:01 [warn]  [RemoteServerController] Ping failed, marking connection dead: RPC call to server.ping timed out
18:11:04 [warn]  [RemoteRPCClient] heartbeat miss 2/2: RPC call to server.ping timed out
18:11:04 [error] [RemoteRPCClient] heartbeat failed 2 times; declaring transport dead and forcing close
18:11:04 [warn]  [RemoteServerController] RPC stream closed
18:11:04 [info]  [RemoteServerController] Auto-reconnect attempt 1 in 845ms (liveProcesses=0, elapsed=0s)
18:11:14 [info]  [BinaryDeployment] Reattached to running server
18:11:15 [info]  [RemoteServerController] Connected to remote server (11397ms, trigger: send_message, reused: true)

Note reused: true — the server is healthy and reattaches cleanly. Only the client-side channel is torn down.

Proof that nothing is reused. 19 directories on the remote for 8 distinct plugins, commit-commands uploaded five separate times:

$ cd ~/.claude/remote/plugins && for d in */; do
    echo "$d $(grep -o '"name"[^,]*' "$d/.claude-plugin/plugin.json" | head -1)"; done
177ae3af63f28fed/  "name": "commit-commands"
249c30ce5738218d/  "name": "playwright"
25908f2528665028/  "name": "commit-commands"
31f1ff22d6b962c1/  "name": "frontend-design"
470cf81c349eee60/  "name": "frontend-design"
6c8831e391f7062f/  "name": "playwright"
7e2f758b6eabcbb8/  "name": "frontend-design"
7f488afb531d59eb/  "name": "commit-commands"
7fb27129915d82b6/  "name": "code-review"
8d8c01a487678747/  "name": "code-simplifier"
9781e4cc2984afad/  "name": "context7"
9f7b2c13062a52f1/  "name": "commit-commands"
a13331603feac916/  "name": "commit-commands"
b0069035e8115ec6/  "name": "claude-md-management"
d6c4f934ea222ca2/  "name": "playwright"
dd1491335d8bb43f/  "name": "context7"
eec1b3cd6c8a89af/  "name": "code-simplifier"
f2eff3797d9747b6/  "name": "code-review"
fde7804946b10701/  "name": "context7"

Counts: commit-commands 5, frontend-design 3, playwright 3, context7 3, code-review 2, code-simplifier 2, claude-md-management 1. Plus 8 orphaned .tar.gz uploads left behind. Three enabled plugins (superpowers, double-shot-latte, and one more) never got a turn at all.

Server-side remote-server.log records the reconnect churn but no error, which is why this is hard to diagnose from the host:

[Server] New connection from: @
[Server] Connection closed: @

Steps to Reproduce

  1. Enable ~10 or more plugins locally, including large ones (superpowers, playwright).
  2. Configure an SSH remote on a host with roughly 200 ms RTT or more. I am in Thailand connecting to a server in Nuremberg, Germany; ~217 ms average, 0% packet loss over 10 pings.
  3. Open a session on that host and send any message.
  4. The session sits at "Setting up plugins..." indefinitely. ssh.log shows the sync/heartbeat-kill/restart cycle repeating.

Ruled out:

  • Ports/firewall — port 22 only, and SSH auth succeeds on every reconnect (USERAUTH_SUCCESS each round).
  • Egress — github.com:443 and the API reachable from the server; a private GitHub marketplace clones fine.
  • Resources — 8 dedicated cores, 16 GB RAM, disk 2% used, node v22.23.2, npm registry reachable.
  • Packet loss — 0% over 10 pings. Latency is high but the link is stable, and plain ssh sessions work normally.
  • VPN — reproduced with the VPN off. The latency is geographic.

Environment

  • Local: Windows 11 Pro 26200, Claude Desktop 1.28929.0.0 (Store/MSIX)
  • Remote: Ubuntu 24.04.4 LTS, x86_64, AMD EPYC 9645, 8 cores, 16 GB
  • Remote server binary: 5db5e4a12f88487e47c2c48259b69a2d630bb3f7
  • Link: ~217 ms RTT, 0% loss

Workaround

Running the Claude Desktop app natively on the remote host and streaming just its window back over xpra seamless (SSH transport) sidesteps this entirely: plugins are installed locally on that host, so no sync over the RPC channel occurs. Reducing the enabled plugin set until it fits inside one heartbeat window also works, but defeats the purpose of keeping one config across machines.

View original on GitHub ↗