From a296ba1374b6f3b0c8e58edfe868f493faa5c6fc Mon Sep 17 00:00:00 2001 From: Afshin Jalili Date: Sun, 8 Nov 2026 21:33:39 +0330 Subject: [PATCH 1/3] fix(web): tell update-all users where to update desktop servers it can't reach Update all skipped every desktop-managed server without the desktop update channel with "needs a newer desktop app". A WSL backend never gets that channel because wsl.exe cannot pass the desktop's control fd, so it was always skipped with that reason, even when its desktop app was current. Both cases (an older desktop app, a WSL backend) are fixed the same way: start the update from the desktop app on that machine. Say that instead, for both the update and the channel-switch skips. Fixes #32062 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Lu2cStAXfXniRZvmstRi38 --- .../sidebar/updateEverything.logic.test.ts | 27 ++++++++++++++++++- .../sidebar/updateEverything.logic.ts | 6 +++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index 58328dbc1eeeb..4fc96b54a8cff 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -85,6 +85,31 @@ describe("collectServerUpdateTargets", () => { ]); }); + it("sends desktop servers without the desktop update channel to that machine's app", () => { + // A WSL backend runs in desktop mode, but wsl.exe cannot pass it the desktop's control fd. + const wsl = machine("wsl-backend", "0.0.40", "desktop-managed"); + const { targets, skipped } = collectServerUpdateTargets( + [ + { + ...wsl, + serverConfig: { + ...wsl.serverConfig!, + environment: { + ...wsl.serverConfig!.environment, + capabilities: { repositoryIdentity: true, serverSelfUpdate: "desktop-managed" }, + }, + }, + }, + ], + "0.0.50", + ); + + expect(targets).toEqual([]); + expect(skipped.map((entry) => [entry.label, entry.reason])).toEqual([ + ["wsl-backend", "update the desktop app on that machine"], + ]); + }); + it("skips servers whose policy refuses the update, with the policy's message", () => { const managed = ( id: string, @@ -239,7 +264,7 @@ describe("collectChannelSwitchTargets", () => { "ahead", `its database has changes ${NIGHTLY} does not support yet; stay on Stable until Nightly has them`, ], - ["old-desktop", "needs a newer desktop app"], + ["old-desktop", "switch the desktop app on that machine"], [ "no-nightly", "Example Corp manages T3 Code updates. Only stable releases can be installed.", diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index cbbd3baa7c776..abaa80d4bc751 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -108,8 +108,10 @@ export function collectServerUpdateTargets( skip("needs a manual update"); continue; } + // Only a desktop app's control channel accepts this request. Older apps lack it, and wsl.exe + // cannot pass it to a WSL backend, so either way the update has to start on that machine. if (selfUpdate === "desktop-managed" && !desktopAppUpdate) { - skip("needs a newer desktop app"); + skip("update the desktop app on that machine"); continue; } const serverVersion = config.environment.serverVersion; @@ -336,7 +338,7 @@ function trackSwitchMove( if (selfUpdate === null) return { skip: "needs a manual update" }; if (selfUpdate !== "desktop-managed") return { selfUpdate }; if (config.environment.capabilities.desktopAppChannelSwitch !== true) { - return { skip: "needs a newer desktop app" }; + return { skip: "switch the desktop app on that machine" }; } // Squirrel.Mac renames the bundle for the new track and then cannot relaunch it. if (!sameTrack && config.environment.platform.os === "darwin") { From 667cb55a4e17eca103326b98352a57deb964c12a Mon Sep 17 00:00:00 2001 From: Afshin Jalili Date: Mon, 9 Nov 2026 00:18:17 +0330 Subject: [PATCH 2/3] fix(web): skip reasons no longer blame the desktop app or misname same-channel updates Address review on #33030: - A WSL server can be behind while its desktop app is current, so the reason no longer assumes the app is stale: "update it on that machine". - A host already on the target channel but behind needs an update, not a channel switch, so the channel-switch skip now uses the update reason there and "switch its channel on that machine" otherwise. Tests extend the existing skip tables instead of adding a new case. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Lu2cStAXfXniRZvmstRi38 --- .../sidebar/updateEverything.logic.test.ts | 56 +++++++------------ .../sidebar/updateEverything.logic.ts | 10 ++-- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index 4fc96b54a8cff..fd280f626036f 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -45,6 +45,20 @@ function machine( }; } +// A desktop-mode server the desktop app cannot control, such as a WSL backend or an older app. +function withoutDesktopControl(base: UpdateEverythingMachine): UpdateEverythingMachine { + return { + ...base, + serverConfig: { + ...base.serverConfig!, + environment: { + ...base.serverConfig!.environment, + capabilities: { repositoryIdentity: true, serverSelfUpdate: "desktop-managed" }, + }, + }, + }; +} + describe("collectServerUpdateTargets", () => { it("updates remote servers that can move to the target and names every skipped one", () => { const { targets, skipped } = collectServerUpdateTargets( @@ -67,6 +81,7 @@ describe("collectServerUpdateTargets", () => { machine("current", "0.0.50", "boot-service"), machine("current-offline", "0.0.50", "boot-service", { connected: false }), machine("manual", "0.0.40", undefined), + withoutDesktopControl(machine("wsl-backend", "0.0.40", "desktop-managed")), ], "0.0.50", ); @@ -82,31 +97,7 @@ describe("collectServerUpdateTargets", () => { ["no-scope", "no permission"], ["busy", "already updating"], ["manual", "needs a manual update"], - ]); - }); - - it("sends desktop servers without the desktop update channel to that machine's app", () => { - // A WSL backend runs in desktop mode, but wsl.exe cannot pass it the desktop's control fd. - const wsl = machine("wsl-backend", "0.0.40", "desktop-managed"); - const { targets, skipped } = collectServerUpdateTargets( - [ - { - ...wsl, - serverConfig: { - ...wsl.serverConfig!, - environment: { - ...wsl.serverConfig!.environment, - capabilities: { repositoryIdentity: true, serverSelfUpdate: "desktop-managed" }, - }, - }, - }, - ], - "0.0.50", - ); - - expect(targets).toEqual([]); - expect(skipped.map((entry) => [entry.label, entry.reason])).toEqual([ - ["wsl-backend", "update the desktop app on that machine"], + ["wsl-backend", "update it on that machine"], ]); }); @@ -240,15 +231,9 @@ describe("collectChannelSwitchTargets", () => { host(machine("service", "0.1.2", "boot-service"), 70), // A Stable tag built a migration the latest Nightly does not have yet. host(machine("ahead", "0.1.3", "boot-service"), 73), - machine("old-desktop", "0.1.2", "desktop-managed", { - serverConfig: { - ...machine("old-desktop", "0.1.2", "desktop-managed").serverConfig!, - environment: { - ...machine("old-desktop", "0.1.2", "desktop-managed").serverConfig!.environment, - capabilities: { repositoryIdentity: true, serverSelfUpdate: "desktop-managed" }, - }, - }, - }), + withoutDesktopControl(machine("old-desktop", "0.1.2", "desktop-managed")), + // Already on Nightly, so it needs an update rather than a channel switch. + withoutDesktopControl(machine("behind-desktop", OLD_NIGHTLY, "desktop-managed")), machine("current", NIGHTLY, "boot-service"), // The server refuses a track its policy rules out, so the dialog skips it up front. managedHost(host(machine("no-nightly", "0.1.2", "boot-service"), 70)), @@ -264,7 +249,8 @@ describe("collectChannelSwitchTargets", () => { "ahead", `its database has changes ${NIGHTLY} does not support yet; stay on Stable until Nightly has them`, ], - ["old-desktop", "switch the desktop app on that machine"], + ["old-desktop", "switch its channel on that machine"], + ["behind-desktop", "update it on that machine"], [ "no-nightly", "Example Corp manages T3 Code updates. Only stable releases can be installed.", diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index abaa80d4bc751..fc9cf2dacec0e 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -66,6 +66,10 @@ export interface SkippedMachine { readonly reason: string; } +// Desktop servers without the desktop control fd (older apps, WSL backends: wsl.exe cannot pass +// fds) cannot be updated from here, and their desktop app is not necessarily the stale part. +const UPDATE_ON_THAT_MACHINE = "update it on that machine"; + /** Skip current and app-hosted servers; the latter update with the app and would relaunch mid-run. * Desktop-managed servers use their own feed. Each server's updater checks that it can install the * release before it restarts, so no registry precheck runs here. */ @@ -108,10 +112,8 @@ export function collectServerUpdateTargets( skip("needs a manual update"); continue; } - // Only a desktop app's control channel accepts this request. Older apps lack it, and wsl.exe - // cannot pass it to a WSL backend, so either way the update has to start on that machine. if (selfUpdate === "desktop-managed" && !desktopAppUpdate) { - skip("update the desktop app on that machine"); + skip(UPDATE_ON_THAT_MACHINE); continue; } const serverVersion = config.environment.serverVersion; @@ -338,7 +340,7 @@ function trackSwitchMove( if (selfUpdate === null) return { skip: "needs a manual update" }; if (selfUpdate !== "desktop-managed") return { selfUpdate }; if (config.environment.capabilities.desktopAppChannelSwitch !== true) { - return { skip: "switch the desktop app on that machine" }; + return { skip: sameTrack ? UPDATE_ON_THAT_MACHINE : "switch its channel on that machine" }; } // Squirrel.Mac renames the bundle for the new track and then cannot relaunch it. if (!sameTrack && config.environment.platform.os === "darwin") { From b794c065a3630df3179c7ea76ea4b05f453cb0a2 Mon Sep 17 00:00:00 2001 From: Afshin Jalili Date: Mon, 9 Nov 2026 01:30:21 +0330 Subject: [PATCH 3/3] fix(web): skip reasons name what to change on that machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skipped list shows only the machine label beside the reason, so "it" didn't say what to update. Name T3 Code, and use "update track" to match Settings → About → Update track. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Lu2cStAXfXniRZvmstRi38 --- .../src/components/sidebar/updateEverything.logic.test.ts | 6 +++--- apps/web/src/components/sidebar/updateEverything.logic.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index fd280f626036f..75f537f6727be 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -97,7 +97,7 @@ describe("collectServerUpdateTargets", () => { ["no-scope", "no permission"], ["busy", "already updating"], ["manual", "needs a manual update"], - ["wsl-backend", "update it on that machine"], + ["wsl-backend", "update T3 Code on that machine"], ]); }); @@ -249,8 +249,8 @@ describe("collectChannelSwitchTargets", () => { "ahead", `its database has changes ${NIGHTLY} does not support yet; stay on Stable until Nightly has them`, ], - ["old-desktop", "switch its channel on that machine"], - ["behind-desktop", "update it on that machine"], + ["old-desktop", "change the update track on that machine"], + ["behind-desktop", "update T3 Code on that machine"], [ "no-nightly", "Example Corp manages T3 Code updates. Only stable releases can be installed.", diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index fc9cf2dacec0e..818ec4d35f49a 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -68,7 +68,7 @@ export interface SkippedMachine { // Desktop servers without the desktop control fd (older apps, WSL backends: wsl.exe cannot pass // fds) cannot be updated from here, and their desktop app is not necessarily the stale part. -const UPDATE_ON_THAT_MACHINE = "update it on that machine"; +const UPDATE_ON_THAT_MACHINE = "update T3 Code on that machine"; /** Skip current and app-hosted servers; the latter update with the app and would relaunch mid-run. * Desktop-managed servers use their own feed. Each server's updater checks that it can install the @@ -340,7 +340,7 @@ function trackSwitchMove( if (selfUpdate === null) return { skip: "needs a manual update" }; if (selfUpdate !== "desktop-managed") return { selfUpdate }; if (config.environment.capabilities.desktopAppChannelSwitch !== true) { - return { skip: sameTrack ? UPDATE_ON_THAT_MACHINE : "switch its channel on that machine" }; + return { skip: sameTrack ? UPDATE_ON_THAT_MACHINE : "change the update track on that machine" }; } // Squirrel.Mac renames the bundle for the new track and then cannot relaunch it. if (!sameTrack && config.environment.platform.os === "darwin") {