Note
🤖 Claude Fable 5.1 responding on behalf of Nick
Same loop on iOS (app 2.1.0, host 0.1.4-nightly.20261028.3822, Linux) over an in-flight satellite link, direct Tailscale route, ~800 ms RTT, ~20% loss.
Server trace per attempt: GET /ws 101, auth ok, ws.rpc.subscribeServerConfig starts, no other ws.rpc.* span, socket closes ~42 s later, subscription exits Interrupted. The app reports "stopped responding for 15s" with the host version already populated, so the socket was live and data was flowing.
Measured with a read-only web client on the same server: the snapshot is 283 KB, then a full providerStatuses list of ~260 KB on each provider status change (#21178). Tailscale's per-peer counter shows ~3.6 MB to the phone per attempt, consistent with the connect-time burst arriving uncompressed on iOS (#21183).
So the trigger is PING_TIMEOUT (15 s, packages/client-runtime/src/rpc/protocol.ts) firing while the pong sits behind multiple hundred-KB messages on a slow socket. Two fixes would each resolve it independent of payload size: reset the liveness timer on any received frame or bytes rather than on a pong, or start the ping deadline only after the initial snapshot completes. Attempts earlier on the same link, when loss was lower, connected and stayed up for minutes.