Skip to content

[Bug]: Android drops a direct environment with "stopped responding" when the server config snapshot is large (~1 MB+) #23731

Open

Description

@Dauslyn

Before submitting

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

Area

apps/mobile

Steps to reproduce

  1. Run a t3 serve 0.1.0 environment with several provider instances (6 Claude/Codex instances plus OpenCode with an OpenRouter catalog of ~466 models) and agents working across many folders.
  2. Pair the Android app (store build 2.0.0, OTA current on 2026-10-22) over a direct Tailscale route.
  3. Open the environment on mobile data.

Expected behavior

The environment connects and stays connected.

Actual behavior

The socket opens, the client sends subscribeServerConfig, receives the snapshot, then never sends another RPC. About 20 s later it closes and shows "Failed to connect. Reconnecting… Reason: stopped responding." It reconnects and drops again in a loop (28 cycles in 15 minutes in our server traces).

What we measured on the server side:

  • The subscribeServerConfig snapshot was 3.1 MB; 2.4 MB of it was providers[].workspaceSnapshots (the duplication described in Provider list carries a full skills and commands copy for every folder, mostly identical to the provider's own #21181). After a service restart it was 0.9 MB and grew back to 1.2 MB within 30 minutes of normal agent use.
  • The server answers Ping in ~2 ms, before and after the snapshot, both on loopback and through its Tailscale HTTPS address.
  • The network path is not the bottleneck: 1 MB reached the phone over the same route in 3.6 s.
  • With the snapshot at 0.9 MB the loop continued. Disabling the OpenCode instance (snapshot 0.57 MB) let the phone connect and stay connected.
  • The same snapshot decodes without error against packages/contracts ServerConfigStreamEvent on current main, so this is not a schema mismatch.
  • A second 0.1.0 environment with a small provider list connects fine from the same phone at the same time.

This looks like the client blocking while it processes a large config snapshot, long enough for the 15 s PING_TIMEOUT in packages/client-runtime/src/rpc/protocol.ts to fire. The same environment held a 15-minute connection earlier the same morning, before the phone picked up a newer OTA.

Impact

Major degradation: the environment is unusable from the phone.

Version or commit

Server 0.1.0; Android app 2.0.0 with OTA from main as of 2026-10-22.

Environment

Android 16 (Samsung S25 Ultra), direct route over Tailscale, mobile data. Server on macOS (arm64).

Logs or stack traces

Server trace, one cycle (okhttp/5.4.0):
GET /ws -> 101, lasted 13–45 s
  ws.rpc.subscribeServerConfig -> Interrupted ("All fibers interrupted without error") when the socket closed
  no other ws.rpc.* spans in the session

Workaround

Restart the server to clear workspaceSnapshots and shrink provider model lists (OpenCode provider.openrouter.whitelist). Both only buy time while #21181 is open.

Activity

  1. Nelglor commented on Oct 29, 2026

    @Nelglor
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions