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]: Remote "update server" from a connected instance kills all in-flight agent turns without warning; app window then closed on its own twice after relaunch #25963
0.1.4-nightly.20261027.3770, macOS desktop, nightly channel (observed after the update). The pre-update version was also a 0.1.4 nightly.
Setup
A macOS desktop instance with six agent sessions running turns, each with a long tool call in progress.
A second T3 Code instance on Linux, connected to the macOS instance through T3 Connect.
Steps to reproduce
Start several agent turns with long-running tool calls on the macOS desktop instance.
From the connected Linux instance, trigger "update server" for the macOS instance.
Observed
Times are relative to the remote request.
T+0: the desktop server logs ws.rpc.server.updateServerWithProgress and cloud.server_self_update.update. The desktop updater downloads the update and calls ElectronUpdater.quitAndInstall.
T+17s: every running provider session receives query.close. provider-runtime.reconcile marks all six in-flight turns cancelled. No warning or confirmation is shown on either instance.
T+27s: the app relaunches. ElectronApp.onBeforeQuitForUpdate fires again, and the app quits again about two minutes later.
This repeats once more: relaunch, onBeforeQuitForUpdate, quit. The app stays up only on the fourth launch, about six minutes after the request.
On each relaunch the app sends a resume message to every thread that had an active turn ("T3 Code restarted and resumed this turn"). The next quit cancels that resume. Each affected session shows the banner three times.
Expected
A remote update request refuses or warns while agent turns are running, or waits for them to go idle.
One restart is enough. The updater does not call quitAndInstall again on the freshly installed build.
Impact
Six sessions lost their in-flight tool calls. Several of those were multi-minute test runs. A separate issue covers the resume after the final relaunch leaving threads in "Working" for about 15 minutes.
Thanks for the detailed timeline. I traced the remote desktop update path on main (e923627) and couldn't find anything that would make a freshly installed build install again:
The desktop app keeps the prepared remote update in memory only, so a relaunched app has nothing to commit. A repeated commit from the connected client fails with "This desktop update is no longer prepared."
The updater runs with auto-download and install-on-quit turned off. A relaunched app installs only when someone triggers it.
ElectronApp.onBeforeQuitForUpdate is logged every time the app starts, because that's when it registers its listener. It doesn't mean an update quit happened. A real updater quit logs desktop.lifecycle.beforeQuitForUpdate and "allowing updater-controlled quit".
So what made the second and third launches quit is still open. To pin that down, could you attach these from the macOS machine, covering the six minutes after the request:
~/.t3/userdata/logs/desktop.trace.ndjson and ~/.t3/userdata/logs/server.trace.ndjson (or excerpts). For each launch we need the app version and the lifecycle span right before the quit (desktop.lifecycle.beforeQuitForUpdate, desktop.lifecycle.beforeQuit, desktop.lifecycle.processSignal or desktop.lifecycle.relaunch).
The app version reported on the second, third and fourth launches. Was it already the new nightly each time?
On the Linux instance, which button you used: the per-server "Update" or "Update everything". Also, did it show an error or a Retry action while the Mac was relaunching?
Whether Settings → Maintenance → "Continue threads after restarts" is on for that environment.
Thanks for checking, and you are right about onBeforeQuitForUpdate: I misread the listener registration as an updater quit. Here is what desktop.trace.ndjson has for the six minutes after the request. Times are UTC.
There is no desktop.lifecycle.beforeQuitForUpdate, processSignal or relaunch span on any launch after the first, and no crash, render-process-gone or unresponsive event anywhere in that window.
What I did: I quit the app once, with Cmd+Q, after the thread showed "T3 Code restarted and resumed this turn" three times and my own follow-up message got no reply. That is the launch 4 quit (beforeQuit before windowAllClosed). I did not close the window on launches 2 or 3, and I did not touch the Linux instance after the update. On those two launches the main window closed on its own 44 s and 126 s after start (windowAllClosed first, beforeQuit 1.5 to 2 s later), and I have no span that says why.
Versions: the startup spans of launches 2 to 5 do not log a version. The installed bundle and server-runtime.json report 0.1.4-nightly.20261027.3770 on launch 5. The updater had downloaded 3770 before launch 2, so launches 2 to 4 most likely ran it too, but the logs do not prove it.
Linux side: most likely "Update everything" (I did not note which button I pressed). It showed no error and no Retry; the update reported success. When I have consciously used the per-server "Update" in the past, it updated only the Linux server.
"Continue threads after restarts": no continue or restart key exists in settings.json, client-settings.json or desktop-settings.json, so it is at the default. The server queued provider-runtime.continue on every start, so it behaved as on.
The effect outbox confirms each quit cancelled the previous resume: for one thread, provider-runtime.continue succeeded at 13:13:51, 13:14:46, 13:16:57 and 13:18:07, and each following provider-turn.start was cancelled with "Cancelled because the server shut down before the provider work completed", until the one from launch 5.
So two things remain here: the remote update ran while six turns were in flight with no warning on either side, and the main window closed by itself twice on the fresh build. I have retitled to match. Tell me if you want the second part split out.
changed the title [-][Bug]: Remote "update server" from a connected instance kills all in-flight agent turns and relaunches the desktop app three times[/-][+][Bug]: Remote "update server" from a connected instance kills all in-flight agent turns without warning; app window then closed on its own twice after relaunch[/+]on Oct 27, 2026
Follow-up from the Linux instance's own logs (the client that sent the update). Times UTC.
13:13:27 "Update everything" started on the Linux instance: its server ran server.updateProvider and its desktop began downloading its own 0.1.4-nightly.20261027.3770.
13:13:34 server.updateServerWithProgress ran on the Linux server and was sent to the macOS environment. The Mac logged the same RPC at the same second.
13:13:49.758 the Linux client sent server.commitDesktopUpdate to the Mac. It failed with SocketCloseError: 1001 (~effect/rpc/RpcClientError), because the Mac was already in quitAndInstall. Every later call to the Mac failed with " is not connected." until it came back.
13:13:55 the Linux desktop quit and relaunched on 3770.
From 13:14:30 to 13:17:57 the Linux client sent no update, commit or relaunch RPC to the Mac. Only server.reportClientActivity, preview.reportProfiles, vcs.refreshStatus and one orchestration.dispatchCommand (a thread visit), all failing with "not connected" while the Mac was down.
So the button was "Update everything", the one failure it produced was the commitDesktopUpdate socket error at 13:13:49 (not shown in the UI, which reported success), and the two unexplained window closes on the Mac at 13:14:44 and 13:16:55 did not come from this client.
I have a multi-machine T3 Code setup and want to update only one chosen environment, without interrupting agent turns still running on the others. I currently find the prominent Update everything flow risky when other machines have active threads.
The updating docs already describe per-environment Settings → Environments → Update server, so the remaining UX request is to make Update this machine only obvious from the global update prompt, with an environment selector and a per-machine count of active turns before confirmation. Ideally it should also offer wait until this machine is idle instead of restarting its running agents.
This relates to the interruption in this report, but is not evidence that T3 lacks a per-environment API. Requested outcomes: selecting machine A must never restart B/C; the updater must clearly identify exactly which agents will be interrupted, and should defer a selected host until idle when requested.
Related headless-service automation requirement, separate from the desktop relaunch diagnosis: is there a supported server-owned wait-until-idle / maintenance admission protocol that prevents new work from starting until an update handoff finishes?
Read-only source inspection of stable v0.1.5 (6d450a4) and nightly v0.1.6-nightly.20261030.3940 (ae521d1) found:
CLI runUpdate explicitly warns restart interrupts turns/terminals/clients and uses confirmation or --yes; noninteractive without --yes leaves activation pending.
cloud/selfUpdate.ts hands the prepared runtime to the launcher.
serviceLauncher.ts validates active child/update/version/runtime/backup capacity, records the pending update, then beginTrial terminates the active child. Those checks serialize updates, but do not appear to reserve admission against new turns.
The updating documentation explicitly warns about interruption. Continue-after-restart is recovery, not preservation of every in-flight tool/terminal process.
An external idle SQL snapshot (even checked twice) has a time-of-check/time-of-use race; I do not propose using it as a restart guarantee. Requested supported contract: server-owned exclusive admission that gates all new work ingress, drains or defers existing work with explicit busy/unknown reasons, holds the reservation through handoff, and releases safely on failure/expiry. Tests should cover a turn arriving between idle observation and update, multiple clients and scheduled/queued starts, and failed/recovered handoff. A documented existing equivalent would also satisfy this dependency.
This is a bounded source inspection and integration requirement, not a new live reproduction or a claim that every API was exhaustively excluded. No new updater implementation or service action is proposed here. Existing SSH external-owner PR #25633 solves a different race and does not establish idle admission.
Here's a proposal for the "wait until idle" requests above, with mockups. It builds on the per-machine dialog from #26210 and the warning in #26424.
Could a maintainer say whether you want this direction before anyone builds it? #12607 and #7503 were closed because they had no prior approval, not because of the idea itself.
What changes
A machine restarts for an update only when it has no tool call or command running. A turn's text and reasoning can still be cut off, and Continue threads after restarts already resumes those.
While it waits, the machine that is about to restart holds new turns, queued messages and scheduled tasks. A client can't make this check safely: new work can start after it looks, and it misses restarts asked for from another machine, which is what happened in this issue.
Dev servers and watchers that no agent waits on don't block the update. The dialog lists them as stopping.
You can still force it. "Restart now" restarts one machine, and "Restart all now" restarts every machine.
If a machine is still busy after 30 minutes, it asks you. You can set it to restart anyway.
The rule counts tool calls rather than unsafe commands because the server can't tell whether a command is safe to repeat. It can see when a tool call item is running (OrchestrationV2TurnItemStatus). Waiting for a fully idle machine doesn't work either. #18024 measured 4 h 08 min with scheduled tasks running.
Confirm
Each row shows what is running on that machine right now.
Waiting
Each machine updates as soon as it is free. This app restarts last.
Updates on their own
This goes in Settings → Maintenance, under Continue threads after restarts. Like that setting, it applies to the machines you select.
Open questions
Should "Update when free" replace "Update" as the default, or sit next to it?
Where an adapter can pause the next tool call (Claude's PreToolUse hook, Codex approvals), should the server pause it to close the gap? Or is restarting in the first free gap enough?
Activity
juliusmarminge commentedon Oct 27, 2026
Thanks for the detailed timeline. I traced the remote desktop update path on main (e923627) and couldn't find anything that would make a freshly installed build install again:
ElectronApp.onBeforeQuitForUpdateis logged every time the app starts, because that's when it registers its listener. It doesn't mean an update quit happened. A real updater quit logsdesktop.lifecycle.beforeQuitForUpdateand "allowing updater-controlled quit".So what made the second and third launches quit is still open. To pin that down, could you attach these from the macOS machine, covering the six minutes after the request:
~/.t3/userdata/logs/desktop.trace.ndjsonand~/.t3/userdata/logs/server.trace.ndjson(or excerpts). For each launch we need the app version and the lifecycle span right before the quit (desktop.lifecycle.beforeQuitForUpdate,desktop.lifecycle.beforeQuit,desktop.lifecycle.processSignalordesktop.lifecycle.relaunch).UkrainianCitizen commentedon Oct 27, 2026
Thanks for checking, and you are right about
onBeforeQuitForUpdate: I misread the listener registration as an updater quit. Here is whatdesktop.trace.ndjsonhas for the six minutes after the request. Times are UTC.Launch 1, pre-update build 0.1.3-nightly.20261027.3754: 13:13:49
desktop.updates.applyUpdateDownloaded(0.1.4-nightly.20261027.3770),DesktopUpdates.installPrepared,desktop.updates.installDownloadedUpdate,desktop.backendInstance.stop; 13:13:54ElectronUpdater.quitAndInstall.Launch 2, 13:14:00, backend ready 13:14:01, startup update check 13:14:07 "no updates available". Quit: 13:14:44.393
desktop.settings.setMainWindowBounds, 13:14:44.396ElectronWindow.clearMainanddesktop.lifecycle.windowAllClosed, 13:14:45.943desktop.lifecycle.beforeQuit("before-quit received"),requestShutdownAndWait,backendInstance.stop.Launch 3, 13:14:49, backend ready 13:14:49, update check 13:15:04 "no updates available". Quit: 13:16:55.166
setMainWindowBounds, 13:16:55.174ElectronWindow.clearMainandwindowAllClosed, 13:16:57.347beforeQuit, shutdown.Launch 4, 13:17:09, backend ready 13:17:10, update check 13:17:24 "no updates available". Quit: 13:18:07.357
beforeQuitfirst, thenwindowAllClosedat 13:18:07.359, shutdown.Launch 5, 13:19:27, backend ready 13:19:35, stays up.
There is no
desktop.lifecycle.beforeQuitForUpdate,processSignalorrelaunchspan on any launch after the first, and no crash, render-process-gone or unresponsive event anywhere in that window.What I did: I quit the app once, with Cmd+Q, after the thread showed "T3 Code restarted and resumed this turn" three times and my own follow-up message got no reply. That is the launch 4 quit (
beforeQuitbeforewindowAllClosed). I did not close the window on launches 2 or 3, and I did not touch the Linux instance after the update. On those two launches the main window closed on its own 44 s and 126 s after start (windowAllClosedfirst,beforeQuit1.5 to 2 s later), and I have no span that says why.Versions: the startup spans of launches 2 to 5 do not log a version. The installed bundle and
server-runtime.jsonreport 0.1.4-nightly.20261027.3770 on launch 5. The updater had downloaded 3770 before launch 2, so launches 2 to 4 most likely ran it too, but the logs do not prove it.Linux side: most likely "Update everything" (I did not note which button I pressed). It showed no error and no Retry; the update reported success. When I have consciously used the per-server "Update" in the past, it updated only the Linux server.
"Continue threads after restarts": no
continueorrestartkey exists insettings.json,client-settings.jsonordesktop-settings.json, so it is at the default. The server queuedprovider-runtime.continueon every start, so it behaved as on.The effect outbox confirms each quit cancelled the previous resume: for one thread,
provider-runtime.continuesucceeded at 13:13:51, 13:14:46, 13:16:57 and 13:18:07, and each followingprovider-turn.startwas cancelled with "Cancelled because the server shut down before the provider work completed", until the one from launch 5.So two things remain here: the remote update ran while six turns were in flight with no warning on either side, and the main window closed by itself twice on the fresh build. I have retitled to match. Tell me if you want the second part split out.
[-][Bug]: Remote "update server" from a connected instance kills all in-flight agent turns and relaunches the desktop app three times[/-][+][Bug]: Remote "update server" from a connected instance kills all in-flight agent turns without warning; app window then closed on its own twice after relaunch[/+]UkrainianCitizen commentedon Oct 27, 2026
Follow-up from the Linux instance's own logs (the client that sent the update). Times UTC.
server.updateProviderand its desktop began downloading its own 0.1.4-nightly.20261027.3770.server.updateServerWithProgressran on the Linux server and was sent to the macOS environment. The Mac logged the same RPC at the same second.server.commitDesktopUpdateto the Mac. It failed withSocketCloseError: 1001(~effect/rpc/RpcClientError), because the Mac was already inquitAndInstall. Every later call to the Mac failed with " is not connected." until it came back.server.reportClientActivity,preview.reportProfiles,vcs.refreshStatusand oneorchestration.dispatchCommand(a thread visit), all failing with "not connected" while the Mac was down.So the button was "Update everything", the one failure it produced was the
commitDesktopUpdatesocket error at 13:13:49 (not shown in the UI, which reported success), and the two unexplained window closes on the Mac at 13:14:44 and 13:16:55 did not come from this client.Majkey25 commentedon Oct 30, 2026
I have a multi-machine T3 Code setup and want to update only one chosen environment, without interrupting agent turns still running on the others. I currently find the prominent Update everything flow risky when other machines have active threads.
The updating docs already describe per-environment Settings → Environments → Update server, so the remaining UX request is to make Update this machine only obvious from the global update prompt, with an environment selector and a per-machine count of active turns before confirmation. Ideally it should also offer wait until this machine is idle instead of restarting its running agents.
This relates to the interruption in this report, but is not evidence that T3 lacks a per-environment API. Requested outcomes: selecting machine A must never restart B/C; the updater must clearly identify exactly which agents will be interrupted, and should defer a selected host until idle when requested.
ViperJuice commentedon Oct 30, 2026
Related headless-service automation requirement, separate from the desktop relaunch diagnosis: is there a supported server-owned wait-until-idle / maintenance admission protocol that prevents new work from starting until an update handoff finishes?
Read-only source inspection of stable v0.1.5 (6d450a4) and nightly v0.1.6-nightly.20261030.3940 (ae521d1) found:
runUpdateexplicitly warns restart interrupts turns/terminals/clients and uses confirmation or --yes; noninteractive without --yes leaves activation pending.cloud/selfUpdate.tshands the prepared runtime to the launcher.serviceLauncher.tsvalidates active child/update/version/runtime/backup capacity, records the pending update, thenbeginTrialterminates the active child. Those checks serialize updates, but do not appear to reserve admission against new turns.An external idle SQL snapshot (even checked twice) has a time-of-check/time-of-use race; I do not propose using it as a restart guarantee. Requested supported contract: server-owned exclusive admission that gates all new work ingress, drains or defers existing work with explicit busy/unknown reasons, holds the reservation through handoff, and releases safely on failure/expiry. Tests should cover a turn arriving between idle observation and update, multiple clients and scheduled/queued starts, and failed/recovered handoff. A documented existing equivalent would also satisfy this dependency.
This is a bounded source inspection and integration requirement, not a new live reproduction or a claim that every API was exhaustively excluded. No new updater implementation or service action is proposed here. Existing SSH external-owner PR #25633 solves a different race and does not establish idle admission.
rgrvlsk commentedon Nov 3, 2026
Here's a proposal for the "wait until idle" requests above, with mockups. It builds on the per-machine dialog from #26210 and the warning in #26424.
Could a maintainer say whether you want this direction before anyone builds it? #12607 and #7503 were closed because they had no prior approval, not because of the idea itself.
What changes
The rule counts tool calls rather than unsafe commands because the server can't tell whether a command is safe to repeat. It can see when a tool call item is
running(OrchestrationV2TurnItemStatus). Waiting for a fully idle machine doesn't work either. #18024 measured 4 h 08 min with scheduled tasks running.Confirm
Each row shows what is running on that machine right now.
Waiting
Each machine updates as soon as it is free. This app restarts last.
Updates on their own
This goes in Settings → Maintenance, under Continue threads after restarts. Like that setting, it applies to the machines you select.
Open questions