fix: reconnect faster after remote server updates - #5404
Conversation
A remote update restarts the server for ~15 seconds, but the client shows "Resuming" for up to ~33s and the relay tunnel comes up ~5s behind the HTTP listener. The client forked a fiber that nudged the connection supervisor once, on the first backoff entry. That nudge fires at ~T+2s while the server is still down for another ~11s, so it is wasted, and the supervisor then climbs its 1/2/4/8/16s ladder with attempts landing at ~3, 5, 9, 17 and 33s. Nudge on every backoff entry instead, paced one second apart, so attempts stay ~1s apart for the whole restart window. The fiber stays a child of the update command, so it is interrupted as soon as the update settles. On the server, drop the 250ms sleep in front of the startup cloud link reconcile. Routes are already serving when activation opens that gate, and the retry schedule covers what the sleep hedged against. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| * Callers fork this as a child of the update command so it is interrupted as | ||
| * soon as the update settles, whether it succeeds, fails, or times out. | ||
| */ | ||
| export function nudgeReconnectDuringUpdateRestart(input: { |
There was a problem hiding this comment.
🟠 High state/server.ts:166
The sleep-then-retry in nudgeReconnectDuringUpdateRestart can tear down a connection it just succeeded in establishing. After filtering a backoff event, the function sleeps for ~1s and then fires retryNow unconditionally. If the supervisor reconnects on its own during that sleep, the stale nudge still sends a retry signal, which causes monitorConnectedLease to return false and tear down the new lease — discarding the successful connection before the lifecycle ready event arrives. Consider re-checking the supervisor state (or making retryNow a no-op when not in backoff) before issuing the nudge.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/client-runtime/src/state/server.ts around line 166:
The sleep-then-retry in `nudgeReconnectDuringUpdateRestart` can tear down a connection it just succeeded in establishing. After filtering a `backoff` event, the function sleeps for ~1s and then fires `retryNow` unconditionally. If the supervisor reconnects on its own during that sleep, the stale nudge still sends a retry signal, which causes `monitorConnectedLease` to return `false` and tear down the new lease — discarding the successful connection before the lifecycle `ready` event arrives. Consider re-checking the supervisor state (or making `retryNow` a no-op when not in `backoff`) before issuing the nudge.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4dedb6a. Configure here.
| ), | ||
| Effect.timeoutOption(SERVER_UPDATE_RESUME_TIMEOUT), | ||
| Effect.ignore, | ||
| ); |
There was a problem hiding this comment.
Stale reconnect nudge race
Medium Severity
nudgeReconnectDuringUpdateRestart sleeps on each backoff entry and then always calls retryNow, without checking whether the supervisor is still backing off. retryNow also tears down an active session, so a reconnect that succeeds during that sleep—common when SubscriptionRef.changes joins mid-backoff or the supervisor’s own 1s timer wins the race—gets interrupted and the resume wait stretches again.
Reviewed by Cursor Bugbot for commit 4dedb6a. Configure here.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. Two unresolved review comments identify a potential race condition where the new reconnect nudge could tear down a successfully established connection if the supervisor reconnects during the 1-second sleep. This bug concern warrants human review before merging. You can customize Macroscope's approvability policy. Learn more. |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 990bb0b)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 990bb0b)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 990bb0b)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 990bb0b)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 990bb0b)
## What's Changed * fix: reconnect faster after remote server updates by @t3dotgg in pingdotgg/t3code#5404 **Full Changelog**: pingdotgg/t3code@v0.0.32-nightly.20260805.1009...v0.0.32-nightly.20260806.1011 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32-nightly.20260806.1011
Imports the tree delta from the fork/candidates tree recorded in the fork-dev/2026-08-06.1 checkpoint to a rebuilt fork/candidates carrying upstream main a2ca89a. Five upstream commits enter the product: a2ca89a feat: native subagent & workflow observability (pingdotgg#5219) 990bb0b fix: reconnect faster after remote server updates (pingdotgg#5404) 7251f1a Prevent terminal loading flash (pingdotgg#5432) 30e4715 fix(web): preserve terminal font size when splitting (pingdotgg#5444) de592a0 Enrich terminal font previews (pingdotgg#5428) importedCandidatesCommit: 9655a9b importedCandidatesTree: 50f9bfa importedUpstreamCommit: a2ca89a previousCandidatesTree: 9b4cd3e Seven files conflicted against the fork/dev product tree. Most are independent additions on both sides and resolve as unions: upstream's backgroundLiveness alongside identity's originSource/participantSummaries, upstream's agent-spawn CTA rows alongside the imported user-input Q&A timeline. Two needed more than a union. apps/mobile threadActivity.ts: upstream's isAgentInternalActivity skip guard must run before identity's resolved-user-input enrichment. Concatenating the sides in the other order would enrich and push agent-internal rows that upstream intends to drop. apps/web Sidebar.logic.ts: the 3-way merge welded upstream's hasPlanReadyPrompt condition onto the "Wake Required" return body, so a plan-ready thread would have rendered as Wake Required and no thread could ever reach Plan Ready. Both branches are restored with their own bodies. The status rank map is merged onto upstream's new scale with Wake Required kept at the Working/Connecting tier, matching its relative position before the import. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Imports the tree delta from the fork/candidates tree recorded in the fork-dev/2026-08-06.1 checkpoint to a rebuilt fork/candidates carrying upstream main a2ca89a. Five upstream commits enter the product: a2ca89a feat: native subagent & workflow observability (pingdotgg#5219) 990bb0b fix: reconnect faster after remote server updates (pingdotgg#5404) 7251f1a Prevent terminal loading flash (pingdotgg#5432) 30e4715 fix(web): preserve terminal font size when splitting (pingdotgg#5444) de592a0 Enrich terminal font previews (pingdotgg#5428) importedCandidatesCommit: 9655a9b importedCandidatesTree: 50f9bfa importedUpstreamCommit: a2ca89a previousCandidatesTree: 9b4cd3e Seven files conflicted against the fork/dev product tree. Most are independent additions on both sides and resolve as unions: upstream's backgroundLiveness alongside identity's originSource/participantSummaries, upstream's agent-spawn CTA rows alongside the imported user-input Q&A timeline. Two needed more than a union. apps/mobile threadActivity.ts: upstream's isAgentInternalActivity skip guard must run before identity's resolved-user-input enrichment. Concatenating the sides in the other order would enrich and push agent-internal rows that upstream intends to drop. apps/web Sidebar.logic.ts: the 3-way merge welded upstream's hasPlanReadyPrompt condition onto the "Wake Required" return body, so a plan-ready thread would have rendered as Wake Required and no thread could ever reach Plan Ready. Both branches are restored with their own bodies. The status rank map is merged onto upstream's new scale with Wake Required kept at the Working/Connecting tier, matching its relative position before the import. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Imports the tree delta from the fork/candidates tree recorded in the fork-dev/2026-08-06.1 checkpoint to a rebuilt fork/candidates carrying upstream main a2ca89a. Five upstream commits enter the product: a2ca89a feat: native subagent & workflow observability (pingdotgg#5219) 990bb0b fix: reconnect faster after remote server updates (pingdotgg#5404) 7251f1a Prevent terminal loading flash (pingdotgg#5432) 30e4715 fix(web): preserve terminal font size when splitting (pingdotgg#5444) de592a0 Enrich terminal font previews (pingdotgg#5428) importedCandidatesCommit: 9655a9b importedCandidatesTree: 50f9bfa importedUpstreamCommit: a2ca89a previousCandidatesTree: 9b4cd3e Seven files conflicted against the fork/dev product tree. Most are independent additions on both sides and resolve as unions: upstream's backgroundLiveness alongside identity's originSource/participantSummaries, upstream's agent-spawn CTA rows alongside the imported user-input Q&A timeline. Two needed more than a union. apps/mobile threadActivity.ts: upstream's isAgentInternalActivity skip guard must run before identity's resolved-user-input enrichment. Concatenating the sides in the other order would enrich and push agent-internal rows that upstream intends to drop. apps/web Sidebar.logic.ts: the 3-way merge welded upstream's hasPlanReadyPrompt condition onto the "Wake Required" return body, so a plan-ready thread would have rendered as Wake Required and no thread could ever reach Plan Ready. Both branches are restored with their own bodies. The status rank map is merged onto upstream's new scale with Wake Required kept at the Working/Connecting tier, matching its relative position before the import. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
First run of the provenance synchronization from [#342](#342) — and the answer to "get the latest upstream onto `fork/dev`". Five upstream commits enter the product: | | | | --- | --- | | `a2ca89aa` | feat: native subagent & workflow observability (pingdotgg#5219) | | `990bb0b6` | fix: reconnect faster after remote server updates (pingdotgg#5404) | | `7251f1a1` | Prevent terminal loading flash (pingdotgg#5432) | | `30e47153` | fix(web): preserve terminal font size when splitting (pingdotgg#5444) | | `de592a00` | Enrich terminal font previews (pingdotgg#5428) | 54 files, +7,055 / −175. ## Checkpoint ```json { "importedCandidatesCommit": "9655a9ba955197361044ef6f8f97e35841ff779e", "importedCandidatesTree": "50f9bfab717c30a8ea90d52060349e209502d116", "importedUpstreamCommit": "a2ca89aa10f13a2222e08afd98c66285121d5ba2", "previousCandidatesTree": "9b4cd3e1c774c3c436e43305c151edf596b2936a" } ``` `previousCandidatesTree` is C1 from tag `fork-dev/2026-08-06.1`. Tag the merge commit `fork-dev/2026-08-06.2` with the values above once this lands. ## Two resolutions worth reviewing Most of the 7 conflicted files are independent additions on both sides and resolve as unions — upstream's `backgroundLiveness` beside identity's `originSource`/`participantSummaries`, upstream's agent-spawn CTA rows beside the imported user-input Q&A timeline. Two were not unions: **`apps/web/src/components/Sidebar.logic.ts`** — the 3-way merge welded upstream's `hasPlanReadyPrompt` condition onto the `"Wake Required"` return body. Left alone, a plan-ready thread would render as **Wake Required** and no thread could ever reach **Plan Ready**. Both branches are restored with their own bodies. The status rank map is merged onto upstream's new scale with `Wake Required` at the `Working`/`Connecting` tier — its relative position before the import. **That tier placement is a judgement call; say if you want it ranked differently.** **`apps/mobile/src/lib/threadActivity.ts`** — upstream's `isAgentInternalActivity` skip guard must run *before* identity's resolved-user-input enrichment. The other order enriches and pushes exactly the agent-internal rows upstream means to drop. ## Validation - All 12 `fork/candidates` commits replayed onto the rebuilt `fork/tim`; `a2ca89aa1` confirmed an ancestor of the new candidates tip. - No residual conflict markers; brace balance checked on the hand-edited files. - **Typecheck and tests have not run locally** — the rebase workspace has no `node_modules`. Fork CI on this PR is the first real verification. Do not merge on the strength of this description. ## Provenance branches not yet pushed `fork/base` → `4a73589` and `fork/tim` → `b1c5fa5` are rebased and `fork/candidates` → `9655a9ba` is rebuilt, but all three are **local only**. The ruleset *Protect fork/tim, candidates, integration* sets `non_fast_forward` with no bypass actor and the app token has no `administration` scope, so I cannot force-push them. Until they are pushed, `importedCandidatesCommit` refers to a commit that exists nowhere on the remote. The tree is what the delta depends on, but the checkpoint is not fully honest until that push happens. Co-authored by [@patroza](https://github.com/patroza) opened by [Patrick Roza](https://discord.com/users/95218063095377920) in chat thread **Discord** · [Discord](https://discord.com/channels/1083767712431480922/1534783738322485399/1534783738322485399) · [T3](https://t3vm/?thread=584a9ad3-243e-4308-8a13-49acdd758b17) Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com> Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 990bb0b)
Rebuilt on main after #5561 and #5404 landed overlapping reconnect work. What main already covers is dropped from this branch: the backoff ladder rework replaces our jitter, wakeProbeFailed replaces our probe-failure fast path, and the authoritative-session guard replaces our shell warm-cache short-circuit. What remains is the mobile-specific delta: - New advisory `network-path-changed` wakeup: WiFi<->cellular keeps isConnected true while invalidating the socket's path, so the mobile adapter emits a wakeup on interface-type changes while active and the supervisor probes the session (3s bound) instead of waiting for the ping timeout. A failed probe rides main's wakeProbeFailed fast path; flapping interfaces cannot cut backoff delays short. - Client activity is re-reported on every newly connected session generation; the AppState-triggered report races the reconnect and is dropped, leaving provider/VCS work paused server-side for up to 25s after a resume. Generation dedup is per-supervisor since replacements restart the counter. - The RPC onPingTimeout hook is wired to logging so zombie-socket disconnects are distinguishable from ordinary closes. - Server websocket idleTimeout drops to 30s from Bun's 120s default (clients ping every 5s, so live connections never idle), releasing a suspended phone's half-dead socket and buffers four times sooner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What's Changed * fix(mobile): reduce thread feed scroll jank by @gabrielelpidio in pingdotgg/t3code#4874 * fix(web): restore sidebar v2 thread actions and terminal icon by @Noojuno in pingdotgg/t3code#4712 * fix(web): settle button now works on hover, not just right-click by @t3dotgg in pingdotgg/t3code#4905 * fix(clients): disable add project while disconnected by @StiensWout in pingdotgg/t3code#4834 * fix(composer): hide default Codex service tier by @maxktz in pingdotgg/t3code#4784 * docs: link iOS and Android app store downloads by @t3dotgg in pingdotgg/t3code#4902 * fix(web): align remote server update action by @StiensWout in pingdotgg/t3code#4731 * fix(connect): suggest a serve command that matches how you ran connect by @t3dotgg in pingdotgg/t3code#4897 * fix(mobile): stop shared content errors in Personal Team builds by @t3dotgg in pingdotgg/t3code#4943 * perf(mobile): sends respond instantly, thread opens stop freezing by @t3dotgg in pingdotgg/t3code#4882 * fix(web): show Codex fast mode as a bolt by @t3dotgg in pingdotgg/t3code#4947 * docs: seed worktrees with a copy of real userdata instead of banning it by @t3dotgg in pingdotgg/t3code#4949 * fix(mobile): support dragged images in the composer by @t3dotgg in pingdotgg/t3code#4953 * fix(mobile): stop long iOS threads from jumping while scrolling up by @t3dotgg in pingdotgg/t3code#4867 * fix(web): keep worktree default when switching a draft's machine by @t3dotgg in pingdotgg/t3code#4964 * perf(mobile): reconnect environments immediately on resume by @t3dotgg in pingdotgg/t3code#4878 * feat(web): pasting a huge screenshot now compresses it instead of erroring by @t3dotgg in pingdotgg/t3code#4967 * feat(web): regenerate thread titles from sidebar by @t3dotgg in pingdotgg/t3code#4810 * fix(web): show server update progress through reconnect by @t3dotgg in pingdotgg/t3code#4903 * feat(search): find threads by conversation content by @t3dotgg in pingdotgg/t3code#4959 * fix: marketing site Vercel builds no longer die after ~100 deploys by @t3dotgg in pingdotgg/t3code#4975 * docs: split user and maintainer docs, fix 100+ stale claims by @t3dotgg in pingdotgg/t3code#4807 * fix(web): server updates no longer look like warnings by @t3dotgg in pingdotgg/t3code#4992 * fix(connect): reboots no longer strand the relay link, 403s now say why by @t3dotgg in pingdotgg/t3code#4988 * Add project file picker (⌘P) and project content search (⇧⌘F) by @jakeleventhal in pingdotgg/t3code#4855 * Check for mobile app updates on launch by @juliusmarminge in pingdotgg/t3code#4958 * fix(mobile): support pre-Liquid-Glass iOS bottom toolbar by @gabrielelpidio in pingdotgg/t3code#4984 * fix(server): restore PR detection without HOME by @StiensWout in pingdotgg/t3code#4985 * fix(web): fill fast mode icon by @maria-rcks in pingdotgg/t3code#5004 * fix: cache project favicons across web and mobile by @gabrielelpidio in pingdotgg/t3code#4767 * perf(ci): cut stale runs and redundant setup by @t3dotgg in pingdotgg/t3code#4802 * style(web): make scroll-to-end pill translucent by @maria-rcks in pingdotgg/t3code#5036 * fix(desktop): bump Clerk Electron SDK to 0.0.24 and register t3code:// scheme on Linux by @juliusmarminge in pingdotgg/t3code#5015 * perf(server): cache default branch name and origin existence across status refreshes by @UtkarshUsername in pingdotgg/t3code#5008 * feat(shared): support shorthand (major-only) versions in the semver helpers by @arhxam in pingdotgg/t3code#5027 * fix(mobile): remove unnecessary photo library permission by @skjiisa in pingdotgg/t3code#4929 * fix(shared): lenient JSON parser deletes commas inside string values by @arhxam in pingdotgg/t3code#5025 * fix(mobile): default bare IP pairing to HTTP by @Lucenx9 in pingdotgg/t3code#4990 * fix(mobile): restore iOS Threads branding by @PixPMusic in pingdotgg/t3code#4862 * chore: add mobile issue template area by @MaxAnderson95 in pingdotgg/t3code#4895 * fix(desktop): link release notes from the update downloaded toast by @Sy-D in pingdotgg/t3code#4771 * fix(mobile): accept Android clipboard images in composer by @adityavardhansharma in pingdotgg/t3code#4836 * fix(mobile): show the correct build channel in Android's Threads page header by @PixPMusic in pingdotgg/t3code#4861 * fix(contracts): decode growing config unions forward-compatibly by @juliusmarminge in pingdotgg/t3code#5055 * fix(mobile): server model, worktree, and origin preferences now apply to new tasks by @t3dotgg in pingdotgg/t3code#5064 * fix(ci): repair the mobile showcase screenshots workflow by @juliusmarminge in pingdotgg/t3code#5057 * fix(ci): capture iPad App Store screenshots in landscape by @PixPMusic in pingdotgg/t3code#5065 * fix(oxlint-plugin): Resolve the oxlint bin without assuming a pnpm layout by @mwolson in pingdotgg/t3code#5066 * feat(cli): `npx t3 pair` - generate QR code from a running server by @t3dotgg in pingdotgg/t3code#4955 * fix(server): self-update no longer rolls itself back on restart by @t3dotgg in pingdotgg/t3code#5095 * fix(ci): rotate iPad showcase captures without Simulator UI scripting by @juliusmarminge in pingdotgg/t3code#5094 * feat(web): render terminals with libghostty-vt by @StiensWout in pingdotgg/t3code#4860 * refactor: move the canonical libghostty-vt vendor to the repository root by @StiensWout in pingdotgg/t3code#5102 * feat(mobile): add thread snoozing by @gabrielelpidio in pingdotgg/t3code#5053 * feat(mobile): make settled threads collapsible by @PixPMusic in pingdotgg/t3code#5056 * follow-up normalization after #4700. by @shivamhwp in pingdotgg/t3code#4498 * feat(web): search threads from the sidebar by @shivamhwp in pingdotgg/t3code#4769 * feat(web): add settings sidebar search by @shivamhwp in pingdotgg/t3code#4682 * fix: threads with open PRs no longer auto-settle by @t3dotgg in pingdotgg/t3code#5151 * fix(server): bound thread catch-up replay and stop full-DB snapshot hydration by @t3dotgg in pingdotgg/t3code#5147 * fix(server): follow branch drift in dedicated worktrees so PRs link to their thread by @t3dotgg in pingdotgg/t3code#5159 * fix(server): make remote updates rollback-safe by @t3dotgg in pingdotgg/t3code#5181 * fix(web): stop settle controls overlapping the status label by @ipanasenko in pingdotgg/t3code#4574 * fix: normalize app icon glyph sizing by @t3-code[bot] in pingdotgg/t3code#5202 * fix(server): surface cloudflared FTL/PNC relay logs as warnings, not debug by @arhxam in pingdotgg/t3code#5076 * fix(server): stop npx service updates from silently leaving the old server running by @t3dotgg in pingdotgg/t3code#5217 * feat: fold legacy models into separate menus by @t3dotgg in pingdotgg/t3code#5190 * fix(desktop): claim the t3code:// scheme default on Linux at startup by @juliusmarminge in pingdotgg/t3code#5054 * fix(web): polish interface spacing by @maria-rcks in pingdotgg/t3code#5252 * fix(desktop): Niri/Hyprland - Linux secret storage backend by @mwolson in pingdotgg/t3code#2916 * fix(web): match loading screen to dark theme by @t3-code[bot] in pingdotgg/t3code#5303 * fix(web): blink the terminal cursor again by @StiensWout in pingdotgg/t3code#5314 * fix(terminal): protect held Ctrl/Cmd+W close shortcut by @StiensWout in pingdotgg/t3code#5322 * fix(server): strip replayable terminal queries from history by @StiensWout in pingdotgg/t3code#5319 * fix(contracts): decode ServerProviders forward-compatibly by @Brechard in pingdotgg/t3code#5327 * fix(web): simplify chat code blocks by @t3-code[bot] in pingdotgg/t3code#5301 * fix(web): align multiline error alert controls by @t3-code[bot] in pingdotgg/t3code#5304 * Upgrade Effect to beta.103 by @juliusmarminge in pingdotgg/t3code#5331 * fix(web): increase tooltip z-index to overlay popovers and menus by @naMqe-h in pingdotgg/t3code#5326 * fix(server): use a Cursor todo's title when its content is blank by @arhxam in pingdotgg/t3code#5073 * fix(ssh): isolate managed tunnel processes by @nateEc in pingdotgg/t3code#4347 * fix(server): scrub AppImage XDG_DATA_DIRS and GSETTINGS_SCHEMA_DIR from terminals by @arhxam in pingdotgg/t3code#5075 * fix(mobile): show correct provider icons for Grok, Cursor, and OpenCode by @Wraient in pingdotgg/t3code#4586 * fix(web): stop the chat timeline reading through the provider status banner by @Bil0000 in pingdotgg/t3code#5353 * fix(desktop,web): contain renderer memory growth and recover from renderer OOM crashes by @t3dotgg in pingdotgg/t3code#5148 * fix(server): generate durable thread titles by @t3dotgg in pingdotgg/t3code#5357 * fix(web): better right panel (new diffs styling) by @maria-rcks in pingdotgg/t3code#5260 * fix(server): keep regenerated titles on topic by @t3dotgg in pingdotgg/t3code#5365 * refactor(server): make title prompts plaintext by @t3dotgg in pingdotgg/t3code#5368 * feat(web): configurable fonts and sizes under Settings → Appearance by @StiensWout in pingdotgg/t3code#5103 * feat(sidebar-v2): bring back thread pinning by @t3dotgg in pingdotgg/t3code#5312 * feat(web): make pairing QR codes actually scannable, with endpoint choice by @t3dotgg in pingdotgg/t3code#5360 * fix(desktop,web): improve in-app browser shortcuts and URL behavior by @t3dotgg in pingdotgg/t3code#4703 * fix(web): prevent legacy model picker layout shift by @FllipEis in pingdotgg/t3code#5349 * fix(server): allow remote updates with database migrations by @t3dotgg in pingdotgg/t3code#5374 * fix(mcp): unblock Kimi models in OpenCode with preview tools by @hwanseoc in pingdotgg/t3code#5128 * fix(web): clear main branch lint warnings by @t3dotgg in pingdotgg/t3code#5384 * fix(mobile): preserve grouped project workspaces by @shivamhwp in pingdotgg/t3code#4642 * fix(mobile): prevent Android thread search crash by @shivamhwp in pingdotgg/t3code#5386 * fix(web): truncate long project switcher names by @FllipEis in pingdotgg/t3code#5348 * fix(mobile): avoid double dividers between thread sections by @shivamhwp in pingdotgg/t3code#5391 * fix(web): keep the composer command menu anchored to the composer by @StiensWout in pingdotgg/t3code#5336 * fix(web): restore terminal link hover styles by @StiensWout in pingdotgg/t3code#5382 * fix(ci): isolate releases from shared API rate limits by @t3dotgg in pingdotgg/t3code#5394 * fix(web): keep model picker shortcuts in sync by @t3dotgg in pingdotgg/t3code#5400 * fix(web): keep terminal font settings reliable by @StiensWout in pingdotgg/t3code#5397 * Enrich terminal font previews by @juliusmarminge in pingdotgg/t3code#5428 * fix(web): preserve terminal font size when splitting by @t3-code[bot] in pingdotgg/t3code#5444 * Prevent terminal loading flash by @juliusmarminge in pingdotgg/t3code#5432 * fix: reconnect faster after remote server updates by @t3dotgg in pingdotgg/t3code#5404 * feat: native subagent & workflow observability by @t3dotgg in pingdotgg/t3code#5219 * perf(server): stop shipping full MCP tool results in thread payloads by @t3dotgg in pingdotgg/t3code#5482 * fix(web): closed plan sidebar stays closed when returning to a thread by @t3dotgg in pingdotgg/t3code#5484 * fix: respect time format for sidebar snooze by @huxcrux in pingdotgg/t3code#4438 * fix: smooth remote server updates by @t3dotgg in pingdotgg/t3code#5470 * fix(server): drop superseded tool updates from snapshots by @t3dotgg in pingdotgg/t3code#5483 * fix(web): clarify auto permission fallback by @t3-code[bot] in pingdotgg/t3code#5431 * fix(acp): keep unknown approvals actionable by @t3-code[bot] in pingdotgg/t3code#5430 * fix(mobile): stop thread messages reading through pending cards by @carlosricojr in pingdotgg/t3code#5450 * fix(web): align composer inline chips with prompt text by @StiensWout in pingdotgg/t3code#5495 * fix(web): clear woke state on explicit thread actions by @StiensWout in pingdotgg/t3code#5486 * fix(server): skip origin fetch when creating worktrees in repos without an origin remote by @t3dotgg in pingdotgg/t3code#5556 * fix(web): update tooltip no longer dismisses when scrolling release notes by @t3dotgg in pingdotgg/t3code#5547 * fix(server): stop showing commit/push/PR notices as errors in the work log by @t3dotgg in pingdotgg/t3code#5559 * fix(web): show remote environment for non-Git projects by @t3dotgg in pingdotgg/t3code#5555 * fix(server): stopping a Claude thread no longer shows an ede_diagnostic error by @t3dotgg in pingdotgg/t3code#5557 * fix(web): show one toast when snoozing threads in bulk by @t3dotgg in pingdotgg/t3code#5560 * fix(server): let stopped threads settle immediately by @t3dotgg in pingdotgg/t3code#5553 * feat: paginate thread loading with user-anchored turn windows by @t3dotgg in pingdotgg/t3code#5493 * fix: prevent reconnect loops during server stalls by @gfsaaser24 in pingdotgg/t3code#5561 * fix(server): settle stopped Claude subagents by @t3dotgg in pingdotgg/t3code#5568 * fix: scrolling up during a running thread no longer snaps back to the bottom by @t3dotgg in pingdotgg/t3code#5566 * fix(server): one disconnecting client no longer blocks every reconnect by @t3dotgg in pingdotgg/t3code#5572 * test(server): catch client transfer regressions in CI by @t3dotgg in pingdotgg/t3code#5350 * fix(web): stop the "requests are slow" warning from firing on every provider update by @t3dotgg in pingdotgg/t3code#5570 * fix(web): keep agent panel rows stable by @t3dotgg in pingdotgg/t3code#5569 * docs: ship production T3 Connect public config in .env.example by @t3dotgg in pingdotgg/t3code#5573 * fix(web): plans stop hijacking the UI, fold into chat instead by @t3dotgg in pingdotgg/t3code#5558 * feat(web): remove Build/Plan toggle from the composer by @t3dotgg in pingdotgg/t3code#5551 * feat(web): per-device provider settings by @t3dotgg in pingdotgg/t3code#4479 * fix(mobile): invisible T3 Connect devices can now be seen and removed by @t3dotgg in pingdotgg/t3code#5563 * fix: add missing space before 'GitHub releases page' link on download page by @Mateleo in pingdotgg/t3code#4511 * fix(web): stop the sidebar "Working" label from pulsing by @t3dotgg in pingdotgg/t3code#5580 * feat(web): add modular theme library by @StiensWout in pingdotgg/t3code#5226 * fix(web): reading a thread clears Done; Woke is dismissible by @t3dotgg in pingdotgg/t3code#5579 * perf(dev): faster cold-start for dev web serving by @t3dotgg in pingdotgg/t3code#5584 * fix(dev): agents get --share right on the first try by @t3dotgg in pingdotgg/t3code#5586 * fix(mobile): improve keyboard avoiding by @jmeistrich in pingdotgg/t3code#5451 * fix(web): live background-work banner no longer hides behind the update notice by @t3dotgg in pingdotgg/t3code#5595 * fix(web): stabilize chat timeline positioning by @jmeistrich in pingdotgg/t3code#5449 * feat(server): record runtime mode per turn and on mode changes by @t3dotgg in pingdotgg/t3code#5593 * feat(web): thread actions from the chat header title by @t3dotgg in pingdotgg/t3code#5592 * fix(mobile): avoid iOS terminal reset on clear by @justynleung in pingdotgg/t3code#5440 * fix(mobile): pad scroll views above Android nav bar by @StoneCotton in pingdotgg/t3code#5415 * fix(mobile): keep Android chat text from showing through the composer by @PollyGlot in pingdotgg/t3code#5582 * fix(mobile): repair Clerk auth navigation headers by @gabrielelpidio in pingdotgg/t3code#5140 * Bump mobile app version to 1.0.2 by @juliusmarminge in pingdotgg/t3code#5588 * feat(web): click the pin icon to unpin a thread by @UtkarshUsername in pingdotgg/t3code#5578 * fix(web): show the correctly matching shortcut in new thread button's tooltip by @UtkarshUsername in pingdotgg/t3code#5594 ## New Contributors * @gabrielelpidio made their first contribution in pingdotgg/t3code#4874 * @arhxam made their first contribution in pingdotgg/t3code#5027 * @skjiisa made their first contribution in pingdotgg/t3code#4929 * @Lucenx9 made their first contribution in pingdotgg/t3code#4990 * @MaxAnderson95 made their first contribution in pingdotgg/t3code#4895 * @t3-code[bot] made their first contribution in pingdotgg/t3code#5202 * @Brechard made their first contribution in pingdotgg/t3code#5327 * @naMqe-h made their first contribution in pingdotgg/t3code#5326 * @Bil0000 made their first contribution in pingdotgg/t3code#5353 * @gfsaaser24 made their first contribution in pingdotgg/t3code#5561 * @Mateleo made their first contribution in pingdotgg/t3code#4511 * @jmeistrich made their first contribution in pingdotgg/t3code#5451 * @justynleung made their first contribution in pingdotgg/t3code#5440 * @StoneCotton made their first contribution in pingdotgg/t3code#5415 * @PollyGlot made their first contribution in pingdotgg/t3code#5582 **Full Changelog**: pingdotgg/t3code@v0.0.31...v0.0.32 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32


A remote server update restarts the server for about 15 seconds, but the client shows "Resuming" for up to ~33s. Separately, the relay tunnel becomes reachable ~5s after the new server starts listening.
Measured from
boot-service.logacross recent updates: old server dies at T+2s, new server listens at T+7-9s, relay tunnel reachable at T+13-15s.Client: nudge reconnect for the whole restart, not just once
The update flow forked a fiber that waited for the first
backoffstate and calledretryNowonce. That nudge lands at ~T+2s while the server is still down for another ~11s, so it is wasted. The supervisor then climbs its normal1/2/4/8/16sladder and attempts land at ~3, 5, 9, 17 and 33s, which is how a ~15s restart becomes a ~33s wait.Now it nudges on every backoff entry, with a one-second sleep before each one, so attempts stay ~1s apart for the whole restart window. The sleep is the pacer: a connection that fails instantly (ECONNREFUSED, or a 502 through the relay) re-enters backoff immediately and would otherwise spin a tight retry loop.
The fiber stays
Effect.forkChild, so it is interrupted as soon as the update command settles. With that in place the 30s cap is unnecessary and now matchesSERVER_UPDATE_RESUME_TIMEOUT, covering the realistic restart window instead of cutting off at 30s.The loop is extracted as
nudgeReconnectDuringUpdateRestartso it is testable without standing up anAtomRegistry; two tests cover the repeated-nudge count and the pacing.Server: what the ~5s actually is
Worth stating plainly, since it changes what is fixable here: the ~5s between
Listening on http://127.0.0.1:3773andRelay client process startedis not local serialization behind activation. Activation opens ~50ms after the listen log, and nothing is logged in the remaining window. The time is spent inside the two relay calls inreconcileDesiredCloudLinkWith, dominated by server-side Cloudflare control-plane work onPOST /v1/client/environment-links— a serial chain of tunnel create, configure, and DNS mutations. Measured locally: secret store I/O is under 1ms total, the ed25519 keypair is genuinely first-run-only, and network transport for both POSTs is ~0.6s.That chain runs on every boot because
releaseManagedTunnelOnShutdowndeletes the tunnel at shutdown (a deliberate Cloudflare per-tunnel billing tradeoff), so the next startup always pays full tunnel-creation latency instead of reusing an existing tunnel.So moving the reconcile earlier would not have bought the 5 seconds — the work is remote and already starts promptly. Keeping the reconcile gated on activation is also the safe choice: it preserves the finalizer ownership rule at
server.ts:564and keeps a rolling-back trial from fighting the previous version for the tunnel. I left that gate alone and removed the one piece of local dead time on the path, the 250ms sleep in front of the first attempt. Routes are already serving when activation opens the gate, and the retry schedule covers what the sleep hedged against.The real leverage is relay-side (keep the tunnel alive across restarts, or parallelize the independent legs of
provision), which is out of scope here.Validation
packages/client-runtimetests: 524 passedapps/servertests: 1846 passed, 7 skippedNote: running the server suite on a machine with a live T3 server leaks
T3_SERVICE_LAUNCHER_CONTEXTinto the test process and fails 125 tests onmainas well. Unsetting it makes the suite pass on bothmainand this branch.Made by Claude Fable 5 running in Claude Code.
Note
Medium Risk
Touches update/reconnect timing and server cloud-link startup reconcile—user-visible recovery paths but localized changes with new tests on the client nudge logic.
Overview
Shortens "Resuming" after a remote server self-update by keeping client reconnect attempts on a ~1s cadence for the whole restart, and trims a small server startup delay before cloud link reconcile.
Client: Replaces the one-shot "first
backoff→retryNow" fiber withnudgeReconnectDuringUpdateRestart, which reacts to every supervisorbackoffduring the update (1s sleep before each nudge so instant failures do not spin). The nudge fiber still forks as a child of the update command and now times out withSERVER_UPDATE_RESUME_TIMEOUTinstead of 30s. Unit tests cover repeated nudges and pacing.Server: Drops the 250ms delay before the first
reconcileDesiredCloudLinkattempt after activation; exponential retry on reconcile is unchanged.Reviewed by Cursor Bugbot for commit 4dedb6a. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Reconnect faster during remote server updates by retrying once per second across the restart window
nudgeReconnectDuringUpdateRestartin server.ts, which watches connection state changes and triggers a retry for everybackoffphase at ~1s intervals untilSERVER_UPDATE_RESUME_TIMEOUTis reached.reconcileDesiredCloudLinkcall in server.ts, so the initial reconciliation starts immediately.📊 Macroscope summarized 4dedb6a. 2 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.