You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: iOS messages queued as "Sends on reconnect" are not sent while the target environment shows Connected #13995
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
In the iOS app (1.3.0), connect to a desktop environment directly over a private HTTPS address. Also have two T3 Connect environments enabled whose machines are currently powered off.
Send messages to threads on the directly connected environment during a brief connection drop.
The messages appear under Unsent as "Sends on reconnect".
Wait for the environment to reconnect.
Expected behavior
Queued messages send as soon as their target environment is connected again.
Actual behavior
At 05:03–05:04 (UTC+10) the Environments sheet showed the target environment as Connected, while two messages to it were still listed as "Sends on reconnect". A persistent "Reconnecting 2 environments" banner was showing because the two T3 Connect machines were offline. The host then restarted for an update (unreachable 05:05:58–05:08:27). The first queued message only reached the host at 05:13:57, about 5½ minutes after the host was back and about 10 minutes after it was written.
Possible cause (a guess, not verified in the client code): the outbox may wait for all environments to reconnect, so offline T3 Connect machines hold back messages for an environment that is online.
The outbox does not wait on every environment. "Sends on reconnect" under Unsent is the static label for a queued new task (ThreadListV2PendingRow). It stays that text even after the target environment is connected. Follow-ups on an existing thread do not use it; those show a queued icon on the thread row.
A queued new task is eligible to send only when that environment's connection phase is connected and its shell status is live (resolveThreadOutboxDeliveryAction in apps/mobile/src/state/thread-outbox-model.ts). Anything else is wait, and the drain moves on to the next queue. Powered-off T3 Connect environments are not a global lock. The "Reconnecting 2 environments" banner is only the count of environments whose phase is connecting or reconnecting (workspaceConnectionStatusLabel). That label is chosen ahead of "Syncing threads...", so a shell that is still synchronizing on the connected machine is hidden while any other environment is reconnecting.
"Connected" on the environment row is the supervisor phase after the websocket is up and the server-config snapshot has arrived. It does not mean the shell subscription has reached live. Until it does, a new task stays queued and the client never calls startThreadTurn. That matches the host trace: markConnected at 05:03:26 with no orchestration.dispatchCommand, then the desktop restart at 05:05:58–05:08:27, then the first startThreadTurn only at 05:13:57.
Outbox retry delay and connection backoff both cap at 16 seconds, so this is not a five-minute timer. credentials-changed only drops relay targets, so the offline T3 Connect supervisors do not reset the direct Tailscale session. A session can sit in shell synchronizing after the row already says Connected (HTTP shell snapshot gives up after 20 seconds and the socket catch-up has no matching client deadline). The "repeated client reconnects" after 05:08:27 would also keep resetting that shell to synchronizing before the send gate opens.
This is a real stall: a queued new task can sit for minutes while its environment shows Connected. The offline machines explain the banner, not the hold. The useful next trace is why that client kept opening new sessions from 05:08:27 until 05:13:57 (session close reasons on the host, or the phone's [t3-thread-outbox] log).
Thanks. On the reconnects between 05:08:27 and 05:13:57: that trace window has rotated out on this host (about 40 minutes of server.trace.ndjson retention), so I can't recover the session close reasons for it. I'll capture the host side promptly at the next desktop update. The iOS client is identifiable by user agent T3Code/85, so I can separate its connects/disconnects and shellSnapshot timing from the other clients.
Possibly related host-side data is in #13994 (comment). On this Windows host, GET /api/orchestration/shell took 53–64 s (in resolveRepositoryIdentitiesForProjects, slowed by degraded Windows process cleanup), while the mobile clients cancelled it at about 6 s. That fits your explanation that the shell never reached live, so the queued send stayed held.
— Geoff. I'm not a developer; an AI agent (Claude) gathered the evidence from my machine and wrote this, so please flag anything that doesn't add up.
No recording or trace to add, as it hasn't recurred. If it does, I'll try disabling only the offline T3 Connect environments, as you suggest, and post a timestamped recording of Unsent and the target's Connected status with the matching host trace.
Activity
juliusmarminge commentedon Sep 27, 2026
Triage
The outbox does not wait on every environment. "Sends on reconnect" under Unsent is the static label for a queued new task (
ThreadListV2PendingRow). It stays that text even after the target environment is connected. Follow-ups on an existing thread do not use it; those show a queued icon on the thread row.A queued new task is eligible to send only when that environment's connection phase is
connectedand its shell status islive(resolveThreadOutboxDeliveryActioninapps/mobile/src/state/thread-outbox-model.ts). Anything else iswait, and the drain moves on to the next queue. Powered-off T3 Connect environments are not a global lock. The "Reconnecting 2 environments" banner is only the count of environments whose phase isconnectingorreconnecting(workspaceConnectionStatusLabel). That label is chosen ahead of "Syncing threads...", so a shell that is still synchronizing on the connected machine is hidden while any other environment is reconnecting."Connected" on the environment row is the supervisor phase after the websocket is up and the server-config snapshot has arrived. It does not mean the shell subscription has reached
live. Until it does, a new task stays queued and the client never callsstartThreadTurn. That matches the host trace:markConnectedat 05:03:26 with noorchestration.dispatchCommand, then the desktop restart at 05:05:58–05:08:27, then the firststartThreadTurnonly at 05:13:57.Outbox retry delay and connection backoff both cap at 16 seconds, so this is not a five-minute timer.
credentials-changedonly drops relay targets, so the offline T3 Connect supervisors do not reset the direct Tailscale session. A session can sit in shellsynchronizingafter the row already says Connected (HTTP shell snapshot gives up after 20 seconds and the socket catch-up has no matching client deadline). The "repeated client reconnects" after 05:08:27 would also keep resetting that shell tosynchronizingbefore the send gate opens.This is a real stall: a queued new task can sit for minutes while its environment shows Connected. The offline machines explain the banner, not the hold. The useful next trace is why that client kept opening new sessions from 05:08:27 until 05:13:57 (session close reasons on the host, or the phone's
[t3-thread-outbox]log).gmelvill commentedon Sep 27, 2026
Thanks. On the reconnects between 05:08:27 and 05:13:57: that trace window has rotated out on this host (about 40 minutes of
server.trace.ndjsonretention), so I can't recover the session close reasons for it. I'll capture the host side promptly at the next desktop update. The iOS client is identifiable by user agentT3Code/85, so I can separate its connects/disconnects andshellSnapshottiming from the other clients.gmelvill commentedon Sep 28, 2026
Possibly related host-side data is in #13994 (comment). On this Windows host,
GET /api/orchestration/shelltook 53–64 s (inresolveRepositoryIdentitiesForProjects, slowed by degraded Windows process cleanup), while the mobile clients cancelled it at about 6 s. That fits your explanation that the shell never reachedlive, so the queued send stayed held.— Geoff. I'm not a developer; an AI agent (Claude) gathered the evidence from my machine and wrote this, so please flag anything that doesn't add up.
gmelvill commentedon Oct 22, 2026
Haven't seen this again recently.
com.t3tools.t3code; desktop 0.1.2-nightly.20261021.3499 (Windows 11, Tailscale Serve).No recording or trace to add, as it hasn't recurred. If it does, I'll try disabling only the offline T3 Connect environments, as you suggest, and post a timestamped recording of Unsent and the target's Connected status with the matching host trace.
juliusmarminge commentedon Nov 12, 2026
This is the same problem as #2829, so I'm closing it in favour of that one. If it is different, say how and it will be reopened.