[Bug]: iPad (iOS app and Safari/Chrome web) reconnect-loops on a remote environment although the server sends the first config snapshot in ~250 ms#16549

opened21d agobyEed-phillips

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

iOS app / web client on iPadOS (shared client runtime, I think)

Steps to reproduce

  1. Run the T3 Code background service (0.0.45) on a Linux desktop, bound to 127.0.0.1:3773, exposed inside a tailnet with tailscale serve (HTTPS 443 → http://127.0.0.1:3773, tailnet only, no Funnel).
  2. Pair an iPad (iPadOS 26.6) with the iOS app via t3 pair --tailscale.
  3. Pair a MacBook (macOS desktop app) the same way, on the same Wi-Fi network.
  4. Leave both connected.

Expected behavior

The iPad stays connected, as the Mac does.

Actual behavior

iOS app (build 106): the environment cycles between connected and reconnecting. From the server trace, each attempt goes:

  • /.well-known/t3/environment 200 → POST /api/auth/websocket-ticket 200 → authenticateWebSocketUpgrade succeeds → /ws upgrade.
  • ws.rpc.subscribeServerConfig (and orchestration.subscribeShell) start.
  • About 14.9 s later, subscribeServerConfig ends Interrupted with ClientAbort.
  • The /ws request closes about 45 s after it opened, and the client retries.

This repeated for over an hour (dozens of attempts). A few connections lasted longer, the longest about 5 min, then the loop resumed.

The ~15 s abort looks like CONNECTION_ESTABLISHMENT_TIMEOUT from #14516. Unlike #14516, the server is not slow. In the same traces, the snapshot's discovery work finishes in well under a second:

  • externalLauncher.resolveAvailableEditors: max 287 ms
  • resolveUsableFileManagerCommand: ~110 ms
  • no other span on the connect path above ~1 s

So the server appears to produce the snapshot in roughly 250 ms, but the iPad client still hits its 15 s budget.

Network ruled out: the loop happens both through the Tailscale DERP relay (70–500 ms) and over a direct path (~30 ms, tailscale ping). Over the same tailscale serve endpoint, on the same Wi-Fi, the macOS desktop app (and Chrome on macOS earlier) stays connected without drops.

Web client on the same iPad:

  • Chrome (iOS, CriOS 154): /pair loads and all assets 200. POST /api/auth/browser-session 200 consumes the pairing token, and the session is created. But the page stays on the T3 loading screen and never requests a websocket ticket or opens /ws. t3 auth session list shows that session with last connected: never. Reloading /pair then re-posts the consumed token, which gets a 401 (UnknownBootstrapCredentialError).
  • Safari (iPadOS 26.6, desktop-class UA): /pair, the manifest and the icon load. No /api/auth/session or /api/auth/browser-session request follows, so the pairing token is never redeemed. It stays on the loading screen.

Impact

Major degradation or frequent failure. The iPad can't be used as a client.

Version or commit

  • Server: t3 0.0.45 (background service, Linux x64)
  • iOS app: T3Code/106 (CFNetwork/3860.700.1, Darwin/25.6.0)
  • Working comparison: macOS desktop app (Electron), same server

Environment

  • Server: Ubuntu 24.04.3 LTS, kernel 6.8, Node via nvm (service PATH resolves claude fine)
  • Client: iPad Air, iPadOS 26.6, Low Power Mode off
  • Transport: tailscale serve HTTPS proxy, Tailscale 1.102.5 on the server

Logs or stack traces

Server trace (server.trace.ndjson), one representative iOS app attempt:

119:13:16 http.server GET /ws 44885ms Success (closed)
219:13:16 ws.rpc.subscribeServerConfig 14868ms Interrupted: InterruptError ... at ClientAbort
3 ├ externalLauncher.resolveAvailableEditors 227ms
4 └ externalLauncher.resolveUsableFileManagerCommand 108ms
519:13:48 (next attempt, same shape)

Chrome on iPad:

119:22:43 POST /api/auth/browser-session 200 (PairingGrantStore.consume Success)
219:22:43 GET /api/auth/session 200
319:22:47 GET /pair 200 (repeats; no /api/auth/websocket-ticket, no /ws)
419:23:32 POST /api/auth/browser-session 401 UnknownBootstrapCredentialError

I don't have client-side traces from the iPad. Happy to collect them if you can point me at how.

Workaround

None on the iPad. The macOS desktop app works.

Additional report and server traces: #24909, iOS 2.1.0 (164), server nightly 20261023.3579. Reconnects continue after fixing the host tailscale CLI access: configuration loads in 0.4–1.4 s, but sockets still last 101 s, 71 s and 0 s, with overlapping old/new sockets. Thread snapshots take 0.1–0.4 s; opened threads can remain on Syncing.