Skip to content

[Bug]: iOS 2.0.0 (103) repeatedly reconnects when opening one existing thread, including after un-settling #15365

Closed

Description

@sergeivad

Before submitting

Area

apps/mobile

Steps to reproduce

Observed repeatedly on this installation; not yet reproduced with a synthetic thread on a clean installation:

  1. Connect the iOS TestFlight app to the Mac Nightly environment through T3 Connect.
  2. Open a small existing native thread and leave the app in the foreground. It stays connected during the observed control intervals.
  3. Open another existing native thread from Settled. Its messages appear, but the app repeatedly reconnects.
  4. Switch back to the small control thread. The connection recovers.
  5. Reopen the affected thread. The reconnect error returns.
  6. Explicitly un-settle the affected thread on the Mac without sending a prompt. Confirm it appears in the active list on both clients. Open it from the active list on the iPhone.
  7. It still shows persistent "Syncing messages..." and the list shows "Reconnecting to MacBook Air ...".

The settled reproduction was observed twice. The active-state test ran for approximately two minutes. The phone stayed in the foreground with Low Power Mode off, and the Mac remained awake. Only Nightly was running; Alpha was closed.

Expected behavior

Opening an existing idle thread should synchronize its history and keep the environment connected. Changing its sidebar settlement status should not be needed to access it.

Actual behavior

The affected thread enters a reconnect loop after its messages load. Observed messages include "Failed to connect. Retrying MacBook Air ...", "Syncing messages...", and "Reconnecting to MacBook Air ...". Earlier connection detail screens also reported a failed connection health check or a disconnected host with a generic DNS/firewall hint.

Other tested threads stay connected on the same phone, Mac, and network. Un-settling the affected thread does not resolve the failure.

Impact

Major degradation or frequent failure. The affected conversation is unreliable to use from the phone.

Version or commit

Mac: 0.0.46-nightly.20261003.2632.
iPhone: TestFlight 2.0.0, build 103. Server user agent identifies the client as T3Code/103.

Environment

MacBook Air, macOS arm64; iPhone over Wi-Fi during the controlled tests; managed T3 Connect Cloudflare tunnel. Mac awake, iPhone Low Power Mode off. The affected native T3 thread currently selects claudeAgent / Claude Opus 5.5. This is a client connection/synchronization failure, not a demonstrated provider failure.

No software update, re-pairing, history deletion, or security-policy change was performed during these tests.

Logs or stack traces

Sanitized server observations, October 4, 2026, Moscow time (UTC+03:00):

First affected-thread test:
01:06:35  SessionStore.markConnected
01:06:48  SessionStore.markDisconnected
01:06:53  SessionStore.markConnected
01:08:01  SessionStore.markDisconnected
01:08:08  SessionStore.markConnected
01:08:40  SessionStore.markDisconnected
01:09:01  SessionStore.markConnected
01:09:36  SessionStore.markDisconnected

Control:
01:10:15  SessionStore.markConnected
          Small thread remains usable; no visible reconnect loop.

Reopen affected thread:
01:11:58  SessionStore.markConnected
01:12:12  SessionStore.markDisconnected
01:12:16  SessionStore.markConnected
01:12:23  SessionStore.markDisconnected
01:12:30  SessionStore.markDisconnected

After explicitly un-settling:
01:17:23  Projection confirms settledOverride=active, settledAt=null.
01:18:09  Affected-thread subscription starts; duration 31.27 s.
01:18:44  Replacement subscription starts; duration 31.59 s.
01:19:18  Replacement subscription starts; duration 27.88 s.
01:18 to 01:20  iPhone shows Syncing messages; list shows Reconnecting.

Concurrent environment descriptor, WebSocket-ticket, and shell requests return HTTP 200; WebSocket upgrades reach and are accepted by the server. Thread snapshot preparation in the observed cycles takes roughly 29 to 156 ms. No HTTP 401 was observed during this controlled reproduction. The server and tunnel processes remain alive with low CPU usage at a sampled point.

Session events can overlap during replacement, and subscription duration does not establish who initiated a close or its cause. The server trace does not expose enough client-side failure detail to determine the exact mechanism.

Read-only stored-data comparison:

Affected thread Small control thread
Message records 104 20
Turn-item records 338 20
Turn-item JSON characters 909,108 22,439
Message JSON characters 73,581 17,832
Linked PRs 3 0

The affected thread has three provider turns. These are SQLite storage measurements, not WebSocket frame sizes. Content size, rendering cost, and linked-PR work are investigation leads, not established causes. A scan found no data:image/ or base64, prefixes in its stored turn items, so the inline-image mechanism in #4595 is not established here.

Workaround

Leaving the affected thread and switching to a smaller tested thread allowed the connection to recover. The affected conversation can be viewed on the Mac. Un-settling it did not help.

Related observations and limits

A 20-second Home Screen suspension recovered in roughly four seconds during a separate test, and a cold app launch recovered. The failure above also occurs without backgrounding or locking the phone.

#4595 concerns inline-image activity payloads on a slow link; #7231 concerns foreground health-probe timeouts; #2750 concerns repeated mobile web reconnects. This report provides a newer native iOS V2 reproduction that persists on Wi-Fi and after un-settling. The exact cause remains unresolved. Mobile connection traces, WebSocket close reasons, and frame sizes during the failing subscription would help.

No conversation contents, credentials, signed asset URLs, or raw trace files are included.

Activity

  1. sergeivad commented on Oct 3, 2026

    @sergeivad
    Author

    Follow-up controlled test on the same builds, October 4, 2026 (Moscow time):

    Sending a new message from the Mac does not clear the failure.

    • At 01:27:28, the Mac submitted a diagnostic request to the affected active thread, instructing the agent to reply only OK and use no tools.
    • At 01:27:37, the agent's OK response was stored. New turn items were only a user message, assistant message, and checkpoint; no tool activity was started.
    • Opening that same thread on the iPhone again produced "Failed to connect. Retrying MacBook Air ..." by 01:28.
    • The server recorded a thread subscription starting at 01:27:47 and ending after 29.00 seconds; the next snapshot preparation started at 01:28:18. Session connect/disconnect events continued.
    • I then force-closed and relaunched only the iPhone T3 app. At 01:29:12, a mobile session connected and the shell request returned HTTP 200 at 01:29:19. The session disconnected at 01:29:40, and the phone's thread list again showed Reconnecting.

    This does not prove the internal cause, but neither un-settling nor appending a fresh successful turn from the Mac resolved the observed client failure. No update, re-pairing, history deletion, or security change was applied.

  2. juliusmarminge commented on Oct 3, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for the careful, controlled comparison, @sergeivad! This looks like a real connection failure, and it isn't a duplicate of #4595, #7231, or #2750. Settlement isn't the cause. Un-settling only changes the sidebar override, and the thread subscription uses the same environment socket either way, which fits the loop continuing from the active list.

    What the code shows

    SessionStore.markConnected and markDisconnected bracket the whole socket in apps/server/src/ws.ts, so each subscription ending means the iPhone dropped the connection, not that the server closed one thread stream. Nothing on that path times out at 30 seconds: RPC pings go every 5 seconds, and the health probe runs only on wakeup with a 15-second deadline (packages/client-runtime/src/connection/supervisor.ts). Because the small thread stays up on the same tunnel, an idle-tunnel timeout is unlikely.

    The part that does match "only this thread" is the snapshot size. The 75-row / 1 MiB cold-open budget in apps/server/src/orchestration-v2/threadHistoryPaging.ts applies only when the timeline has no turn-start anchors. When anchors exist, the snapshot query in ProjectionStore.ts has no row cap and keeps the latest user turns (up to 10) intact, with every tool item. So a three-turn thread is sent in full, and payloadBudgetExceeded is reported without truncating it. The wire snapshot also duplicates local rows into visibleTurnItems and turnItems, so it can be larger than the ~909 KB stored. "Syncing messages..." is the pill ThreadDetailScreen.tsx shows when messages are on screen but the synchronized marker hasn't arrived yet, which fits a socket that keeps dying before then.

    Likely fix area

    • One option is to apply a size budget to anchored snapshots too, so a thread with a few very large turns doesn't ship its whole timeline at once.
    • Another is to stop sending the duplicated rows in visibleTurnItems and turnItems.

    One check that would help

    If you can, open this same thread in the desktop app or a browser through the same T3 Connect hostname. If that client also reconnects, the full snapshot is the likely cause. If only the phone does, the WebSocket close code from the iOS connection screen would be the next useful detail. No trace contents are needed.

    A maintainer will decide on the fix direction.

  3. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 3, 2026
  4. sergeivad commented on Oct 4, 2026

    @sergeivad
    Author

    I tried the requested comparison using an in-app desktop browser at the same existing managed T3 Connect hostname (not localhost). I could not reach the thread: the web client stays on its boot splash screen, and the browser console reports:

    Clerk: Production Keys are only allowed for domain "t3.codes".
    The Request HTTP Origin header must be equal to or a subdomain of the requesting URL.

    The hostname is under *.t3coderelay.com. No certificate, authentication, or security protection was bypassed. This means the browser test is inconclusive for the snapshot hypothesis. Is there a supported client entry point for testing the same managed tunnel in a browser?

    A separate mobile observation on the same builds: Settings > Environments temporarily showed 0 environments and "No additional linked cloud environments", while Account > T3 Connect still listed the linked Mac. Force-closing and relaunching only the iOS app restored the existing Mac to Environments with "Connected"; no re-pairing was needed. This restores the inventory, but does not establish that the affected large-thread disconnect is fixed.

    The expanded iOS connection card currently shows Connected and managed-tunnel controls, without a numeric WebSocket close code. The failure screenshots previously attached show the human-readable health-check/disconnected messages. Where can I obtain the numeric close code in build 103?

  5. jonnotk commented on Oct 16, 2026

    @jonnotk

    A server-side trace of the same symptom, from a web client on a self-hosted Linux server (0.1.1-nightly.20261016.3190, Docker, QNAP NAS). Opening one existing thread made the client reconnect, and the trace shows why: the thread snapshot query held the main thread for 29 seconds.

    Setup. statev2.sqlite is 3.19 GB, with 69,977 turn items and 333,032 events, on a two-disk 5400 rpm RAID1. The NAS had rebooted four hours earlier. The thread has 8,730 turn items (59 MB of payload_json, largest row 1.08 MB) and 326 runs.

    What happened, 2026-10-16 UTC (from server.trace.ndjson and event-loop-stalls.ndjson):

    Time Span Duration
    16:30:46 ws.rpc.orchestration.subscribeThread 48.5 s
    16:30:46 ws.orchestrationV2.threadSnapshotThenLive → sql.transaction 36.8 s
    16:30:46 sqlite WITH eligible AS NOT MATERIALIZED (...) (readCanonicalProjection in ProjectionStore.ts) 29.1 s
    16:31:15 SessionStore.verify (WebSocket upgrade auth), queued behind it 8.0 s
    16:31:15 ScheduledTaskService.listDueTasks, UsageLimitRecoveryWorker.sweep, PRAGMA auto_vacuum 8.2 s each

    Event-loop stall records in that window: delayMaxMs 3,140 and 6,206, utilization 0.9 and 1.0. The web client's RPC failed with SocketReadError: An error occurred during Read, and requests sent during the stall reached the server about 20 seconds late. The session-state read failed after its 6 s client timeout.

    Same thread, data in memory. A full scan of the thread's 8,730 payload_json rows took 0.1 s from Python against the same file, a few minutes later. So the 29 s came from reading pages from disk while the query ran on the main thread. 1,851 git commands from PR lookups were hitting the same disks at that time. That part is inferred: I did not drop the host's page cache to reproduce it.

    What would help. Any one of these keeps a slow disk read from freezing the whole server:

    1. Run the projection reads for thread snapshots off the main thread (a worker or a read connection on its own thread), so auth, scheduled tasks and other sockets keep moving while one large thread loads.
    2. Bound the eligible scan before it touches payload_json. It filters every turn item of the thread and calls json_extract(item.payload_json, ...) for user_message rows. A column for inputIntent and createdBy, or an index that covers the turn-start rows, would let the window pick its rows from the index alone and read payloads only for the selected rows.
    3. Warm the database file into the page cache at server start. We do this now as a workaround: one sequential read of the database file at low I/O priority in the container entrypoint. On spinning disks that is far cheaper than the random reads the first snapshot does.

    Related: #14701 (the thread list read blocking the loop, closed by #15033) and #20254 (VCS refresh stalls on a VPS).

  6. t3dotgg commented on Oct 21, 2026

    @t3dotgg
    Member

    Note

    🤖 Claude Opus 5.5 responding on behalf of Theo

    Thanks for the report. One part of this is fixed on main in #15385. Please test on the nightly built from main on 2026-10-21, or on the next stable release. One part is still open: Anchored snapshots remain uncapped, and the reported 900 KB thread's reconnect loop is not proven fixed. If that part still matters to you, please open a new issue for that part only.

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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions