# Files changed · PR \#23007 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23007/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(desktop\): SSH launcher waits for desktop updates before starting a server](https://git.cafe/pingdotgg/t3code/pull/23007.md)

Version: 23

Head commit: 87ac5b3a34fe2c12561bf16f6f823e0376fda004

Base commit: 8d3b061121fe65669d5d43d011b48cd3027a4108

Comparison base: 8d3b061121fe65669d5d43d011b48cd3027a4108

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## \.gitignore

Status: modified; +2; −0

```diff
@@ -46,3 +46,5 @@
 
 # pnpm content-addressable store; never belongs in the repo.
 .pnpm-store/
+
+/packages/ssh/.ssh-launcher-test-*/
```

## apps/desktop/src/backend/DesktopBackendPool.test.ts

Status: modified; +133; −7

```diff
@@ -1,11 +1,18 @@
 import { assert, describe, it } from "@effect/vitest";
+import * as Deferred from "effect/Deferred";
+import * as Duration from "effect/Duration";
+import * as TestClock from "effect/testing/TestClock";
+import * as Sink from "effect/Sink";
 import * as Effect from "effect/Effect";
 import * as FileSystem from "effect/FileSystem";
 import * as Layer from "effect/Layer";
+import * as Path from "effect/Path";
+import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
+import * as DesktopConfig from "../app/DesktopConfig.ts";
 import * as Option from "effect/Option";
 import * as Ref from "effect/Ref";
 import * as Stream from "effect/Stream";
-import { HttpClient } from "effect/http";
+import { HttpClient, HttpClientResponse } from "effect/http";
 import { ChildProcessSpawner } from "effect/process";
 
 import * as DesktopObservability from "../app/DesktopObservability.ts";
@@ -20,18 +27,44 @@
 import * as DesktopBackendPool from "./DesktopBackendPool.ts";
 import type { DesktopBackendStartConfig } from "./DesktopBackendManager.ts";
 
+function layerPool(
+  labelRef: Ref.Ref<string>,
+  options: {
+    readonly config?: DesktopBackendStartConfig;
+    readonly fileSystem?: Partial<FileSystem.FileSystem>;
+    readonly spawner?: ChildProcessSpawner.ChildProcessSpawner["Service"];
+    readonly httpClient?: HttpClient.HttpClient;
+    readonly onReady?: Effect.Effect<void>;
+  } = {},
-function layerPool(labelRef: Ref.Ref<string>): Layer.Layer<DesktopBackendPool.DesktopBackendPool> {
+): Layer.Layer<DesktopBackendPool.DesktopBackendPool> {
   return DesktopBackendPool.layer.pipe(
     Layer.provideMerge(
       Layer.mergeAll(
+        DesktopEnvironment.layer({
+          dirname: "/repo/apps/desktop/src",
+          homeDirectory: "/test-home",
+          platform: "linux",
+          processArch: "x64",
+          appVersion: "1.2.3",
+          appPath: "/repo",
-        FileSystem.layerNoop({}),
+          isPackaged: true,
+          resourcesPath: "/missing/resources",
+          runningUnderArm64Translation: false,
+        }).pipe(
+          Layer.provide(
+            Layer.mergeAll(Path.layer, DesktopConfig.layerTest({ T3CODE_HOME: "/test-home" })),
+          ),
+          Layer.orDie,
+        ),
+        FileSystem.layerNoop(options.fileSystem ?? {}),
         Layer.succeed(
           ChildProcessSpawner.ChildProcessSpawner,
+          options.spawner ??
+            ChildProcessSpawner.make(() => Effect.die("unexpected child process spawn")),
-          ChildProcessSpawner.make(() => Effect.die("unexpected child process spawn")),
         ),
         Layer.succeed(
           HttpClient.HttpClient,
-          HttpClient.make(() => Effect.die("unexpected HTTP request")),
+          options.httpClient ?? HttpClient.make(() => Effect.die("unexpected HTTP request")),
         ),
         Layer.succeed(DesktopObservability.DesktopBackendOutputLogFactory, {
           forInstance: () =>
@@ -56,7 +89,9 @@
         }),
         DesktopBrowserHost.layer,
         Layer.succeed(DesktopBackendConfiguration.DesktopBackendConfiguration, {
-          resolvePrimary: Effect.die("unexpected primary config resolve"),
+          resolvePrimary: options.config
+            ? Effect.succeed(options.config)
+            : Effect.die("unexpected primary config resolve"),
           resolvePrimaryLabel: Ref.get(labelRef),
           resolveWsl: () => Effect.die("unexpected WSL config resolve"),
           currentBootstrapToken: Effect.die("unexpected bootstrap token read"),
@@ -72,7 +107,7 @@
           activate: Effect.die("unexpected window activate"),
           createMainIfBackendReady: Effect.die("unexpected window create"),
           showConnectingSplash: Effect.void,
-          handleBackendReady: () => Effect.void,
+          handleBackendReady: () => options.onReady ?? Effect.void,
           handleBackendNotReady: Effect.void,
           flushMainWindowBounds: Effect.void,
           prepareCaptureReveal: Effect.void,
@@ -88,6 +123,97 @@
 }
 
 describe("DesktopBackendPool", () => {
+  it.effect.each(["native", "WSL"] as const)(
+    "%s primary retains the update marker through an eight-second boot gap and clears it on readiness",
+    (backendMode) =>
+      Effect.scoped(
+        Effect.gen(function* () {
+          const probeStarted = yield* Deferred.make<void>();
+          const allowReady = yield* Deferred.make<void>();
+          const ready = yield* Deferred.make<void>();
+          const exited = yield* Deferred.make<ChildProcessSpawner.ExitCode>();
+          const markerPath = `/test-home/runtime/desktop-update-in-progress`;
+          const files = new Map([
+            [markerPath, JSON.stringify({ startedAt: 0, expiresAt: 60_000 })],
+          ]);
+          const removed: string[] = [];
+          const config: DesktopBackendStartConfig = {
+            executablePath: "/electron",
+            args: [],
+            entryPath: "/server/bin.mjs",
+            cwd: "/server",
+            env: {},
+            bootstrap: {
+              mode: "desktop",
+              noBrowser: true,
+              port: 3774,
+              ...(backendMode === "native" ? { t3Home: "/test-home" } : {}),
+              host: "127.0.0.1",
+              desktopBootstrapToken: "token",
+              tailscaleServeEnabled: false,
+              tailscaleServePort: 443,
+            },
+            bootstrapDelivery: "fd3",
+            extendEnv: true,
+            httpBaseUrl: new URL("http://127.0.0.1:3774"),
+            captureOutput: false,
+            preflightFailure: Option.none(),
+          };
+          const layer = layerPool(Ref.makeUnsafe("Local"), {
+            config,
+            fileSystem: {
+              exists: (path) => Effect.succeed(files.has(path) || path === config.entryPath),
+              readFileString: (path) => Effect.succeed(files.get(path) ?? ""),
+              remove: (path) =>
+                Effect.sync(() => {
+                  removed.push(path);
+                  files.delete(path);
+                }),
+            },
+            spawner: ChildProcessSpawner.make(() =>
+              Effect.succeed(
+                ChildProcessSpawner.makeHandle({
+                  pid: ChildProcessSpawner.ProcessId(123),
+                  stdout: Stream.empty,
+                  stderr: Stream.empty,
+                  all: Stream.empty,
+                  exitCode: Deferred.await(exited),
+                  isRunning: Effect.succeed(true),
+                  kill: () =>
+                    Deferred.succeed(exited, ChildProcessSpawner.ExitCode(0)).pipe(Effect.asVoid),
+                  stdin: Sink.drain,
+                  getInputFd: () => Sink.drain,
+                  getOutputFd: () => Stream.empty,
+                  unref: Effect.succeed(Effect.void),
+                }),
+              ),
+            ),
+            httpClient: HttpClient.make((request) =>
+              Deferred.succeed(probeStarted, undefined).pipe(
+                Effect.andThen(Deferred.await(allowReady)),
+                Effect.as(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }))),
+              ),
+            ),
+            onReady: Deferred.succeed(ready, undefined).pipe(Effect.asVoid),
+          });
+          yield* Effect.gen(function* () {
+            const pool = yield* DesktopBackendPool.DesktopBackendPool;
+            const primary = yield* pool.primary;
+            yield* primary.start;
+            yield* Deferred.await(probeStarted);
+            yield* TestClock.adjust(Duration.seconds(8));
+            assert.isTrue(files.has(markerPath), "starting a replacement is not readiness");
+            assert.deepEqual(removed, []);
+            yield* Deferred.succeed(allowReady, undefined);
+            yield* Deferred.await(ready);
+            assert.isTrue(yield* primary.waitForReady(Duration.seconds(1)));
+            assert.isFalse(files.has(markerPath), "readiness must clear the handoff marker");
+            assert.deepEqual(removed, [markerPath]);
+          }).pipe(Effect.provide(layer));
+        }),
+      ),
+  );
+
   it.effect("unregister stops the registered instance", () =>
     Effect.scoped(
       Effect.gen(function* () {
```

## apps/desktop/src/backend/DesktopBackendPool.ts

Status: modified; +21; −6

```diff
@@ -1,3 +1,4 @@
+import { DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE } from "@t3tools/contracts";
 import * as Context from "effect/Context";
 import * as Deferred from "effect/Deferred";
 import * as Effect from "effect/Effect";
@@ -14,6 +15,7 @@
 
 import * as DesktopBackendConfiguration from "./DesktopBackendConfiguration.ts";
 import * as DesktopBackendManager from "./DesktopBackendManager.ts";
+import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
 import * as DesktopObservability from "../app/DesktopObservability.ts";
 import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
 import * as DesktopTelemetryPublisher from "../telemetry/DesktopTelemetryPublisher.ts";
@@ -101,6 +103,8 @@
 export const layer = Layer.effect(
   DesktopBackendPool,
   Effect.gen(function* () {
+    const environment = yield* DesktopEnvironment.DesktopEnvironment;
+    const fileSystem = yield* FileSystem.FileSystem;
     const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
     const desktopWindow = yield* DesktopWindow.DesktopWindow;
     const electronDialog = yield* ElectronDialog.ElectronDialog;
@@ -167,13 +171,24 @@
       // Window creation errors propagating out of handleBackendReady must not block the readiness
       // callback (that would prevent restartAttempt from being reset), so we absorb them here.
       onReady: (httpBaseUrl) =>
+        fileSystem
+          .remove(
+            environment.path.join(
+              environment.baseDir,
+              "runtime",
+              DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE,
+            ),
+            { force: true },
+          )
+          .pipe(
+            Effect.ignore,
+            Effect.andThen(desktopWindow.handleBackendReady(httpBaseUrl)),
+            Effect.catch((error) =>
+              logBackendPoolWarning("failed to open main window after backend readiness", {
+                error: error.message,
+              }),
+            ),
-        desktopWindow.handleBackendReady(httpBaseUrl).pipe(
-          Effect.catch((error) =>
-            logBackendPoolWarning("failed to open main window after backend readiness", {
-              error: error.message,
-            }),
           ),
-        ),
       onShutdown: () => desktopWindow.handleBackendNotReady,
       onPreflightFailed: handlePrimaryPreflightFailure,
       onStateDirOwned: () =>
```

## apps/desktop/src/updates/DesktopUpdates.test.ts

Status: modified; +129; −0

```diff
@@ -412,6 +412,135 @@
     ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
   });
 
+  it.effect("publishes a 60-second update-in-progress marker before stopping the backend", () => {
+    const now = Date.parse("2026-10-21T12:00:00.000Z");
+    let markerAtStop: string | undefined;
+    const harness = makeHarness({
+      stopBackend: Effect.sync(() => {
+        assert.isTrue(
+          harness.markerOperations.some((operation) => operation.startsWith("rename ")),
+        );
+        assert.isFalse(
+          [...harness.updateMarkerContents.keys()].some((path) => path.endsWith(".tmp")),
+        );
+        markerAtStop = [...harness.updateMarkerContents].find(([path]) =>
+          path.endsWith("/desktop-update-in-progress"),
+        )?.[1];
+      }),
+    });
+
+    return Effect.scoped(
+      Effect.gen(function* () {
+        yield* TestClock.setTime(now);
+        const updates = yield* DesktopUpdates.DesktopUpdates;
+        yield* updates.configure;
+        harness.emit("update-downloaded", { version: "1.2.4" });
+        yield* flushCallbacks;
+        assert.isTrue((yield* updates.install).accepted);
+        assert.isDefined(markerAtStop, "the marker must already exist at backend stop");
+        assert.isNotEmpty(
+          markerAtStop!,
+          "an empty marker cannot tell an SSH launcher when to stop waiting",
+        );
+        const marker = JSON.parse(markerAtStop!);
+        assert.isNumber(marker.expiresAt);
+        assert.equal(marker.startedAt, now);
+        assert.equal(marker.expiresAt, now + 60_000);
+        assert.include(
+          [...harness.updateMarkerContents.values()],
+          markerAtStop!,
+          "a successful installer handoff retains the marker for reconnect",
+        );
+      }),
+    ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
+  });
+
+  it.effect("cleans the temporary marker when atomic publication fails", () => {
+    const harness = makeHarness({
+      failMarkerRename: true,
+      stopBackend: Effect.sync(() => {
+        assert.isFalse(
+          [...harness.updateMarkerContents.keys()].some((path) =>
+            path.includes("desktop-update-in-progress"),
+          ),
+        );
+      }),
+    });
+    return Effect.scoped(
+      Effect.gen(function* () {
+        const updates = yield* DesktopUpdates.DesktopUpdates;
+        yield* updates.configure;
+        harness.emit("update-downloaded", { version: "1.2.4" });
+        yield* flushCallbacks;
+        assert.isTrue((yield* updates.install).accepted);
+        assert.isTrue(
+          harness.markerOperations.some((operation) =>
+            operation.endsWith("desktop-update-in-progress.tmp"),
+          ),
+        );
+      }),
+    ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
+  });
+
+  it.effect("clears the update-in-progress marker when an install is interrupted", () =>
+    Effect.gen(function* () {
+      const stopping = yield* Deferred.make<void>();
+      const harness = makeHarness({
+        stopBackend: Deferred.succeed(stopping, undefined).pipe(Effect.andThen(Effect.never)),
+      });
+      yield* Effect.scoped(
+        Effect.gen(function* () {
+          const updates = yield* DesktopUpdates.DesktopUpdates;
+          yield* updates.configure;
+          harness.emit("update-downloaded", { version: "1.2.4" });
+          yield* flushCallbacks;
+          const install = yield* updates.install.pipe(Effect.forkScoped);
+          yield* Deferred.await(stopping);
+          const markerPath = [...harness.updateMarkerContents.keys()].find((path) =>
+            path.endsWith("/desktop-update-in-progress"),
+          );
+          assert.isDefined(markerPath, "the install must publish its handoff before stopping");
+          yield* Fiber.interrupt(install);
+          assert.isFalse(harness.updateMarkerContents.has(markerPath!));
+        }),
+      ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
+    }),
+  );
+
+  it.effect(
+    "clears the update-in-progress marker when the native installer refuses the update",
+    () => {
+      let markerAtStop: string | undefined;
+      const harness = makeHarness({
+        stopBackend: Effect.sync(() => {
+          markerAtStop = [...harness.updateMarkerContents.keys()].find((path) =>
+            path.endsWith("/desktop-update-in-progress"),
+          );
+        }),
+        quitAndInstall: Effect.fail(
+          new ElectronUpdater.ElectronUpdaterQuitAndInstallError({
+            channel: "latest",
+            isSilent: true,
+            isForceRunAfter: true,
+            cause: new Error("installer refused"),
+          }),
+        ),
+      });
+      return Effect.scoped(
+        Effect.gen(function* () {
+          const updates = yield* DesktopUpdates.DesktopUpdates;
+          yield* updates.configure;
+          harness.emit("update-downloaded", { version: "1.2.4" });
+          yield* flushCallbacks;
+          const result = yield* updates.install;
+          assert.equal(result.state.errorContext, "install");
+          assert.isDefined(markerAtStop, "the install must publish its handoff before stopping");
+          assert.isFalse(harness.updateMarkerContents.has(markerAtStop!));
+        }),
+      ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
+    },
+  );
+
   it.effect("records the version an install hands to the installer", () => {
     let recordsAtStop: ReadonlyArray<string> = [];
     const harness = makeHarness({
```

## apps/desktop/src/updates/DesktopUpdates.ts

Status: modified; +33; −0

```diff
@@ -1,5 +1,7 @@
 import {
   DESKTOP_UPDATE_RESTART_MARKER_FILE,
+  DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE,
+  DESKTOP_UPDATE_IN_PROGRESS_TTL_MS,
   DesktopUpdateChannelSchema,
   type DesktopRuntimeInfo,
   type DesktopUpdateActionResult,
@@ -597,6 +599,34 @@
       ),
     );
 
+  const updateInProgressMarkerPath = environment.path.join(
+    updateRestartMarkerDir,
+    DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE,
+  );
+  const encodeUpdateMarker = Schema.encodeEffect(
+    Schema.fromJsonString(Schema.Struct({ startedAt: Schema.Number, expiresAt: Schema.Number })),
+  );
+  const updateInProgressTempPath = `${updateInProgressMarkerPath}.tmp`;
+  const writeUpdateInProgressMarker = Effect.gen(function* () {
+    const startedAt = DateTime.toEpochMillis(yield* DateTime.now);
+    const contents = yield* encodeUpdateMarker({
+      startedAt,
+      expiresAt: startedAt + DESKTOP_UPDATE_IN_PROGRESS_TTL_MS,
+    });
+    yield* fileSystem.writeFileString(updateInProgressTempPath, contents);
+    yield* fileSystem.rename(updateInProgressTempPath, updateInProgressMarkerPath);
+  }).pipe(
+    Effect.ensuring(
+      fileSystem.remove(updateInProgressTempPath, { force: true }).pipe(Effect.ignore),
+    ),
+    Effect.catch((error) =>
+      logUpdaterWarning("Could not write the update-in-progress marker.", { errorTag: error._tag }),
+    ),
+  );
+  const removeUpdateInProgressMarker = fileSystem
+    .remove(updateInProgressMarkerPath, { force: true })
+    .pipe(Effect.ignore);
+
   // A failed or interrupted install brings no updated backend, so a later
   // quit must release the tunnel.
   const removeUpdateRestartMarker = fileSystem
@@ -648,6 +678,7 @@
       Ref.set(desktopState.quitting, false),
       Ref.set(installRunningBackendsRef, []),
       removeUpdateRestartMarker,
+      removeUpdateInProgressMarker,
       removeInstallRecord,
     ],
     { discard: true },
@@ -665,6 +696,7 @@
 
     yield* Ref.set(desktopState.quitting, false);
     yield* removeUpdateRestartMarker;
+    yield* removeUpdateInProgressMarker;
     yield* removeInstallRecord;
     yield* Effect.gen(function* () {
       const runningInstances = yield* Ref.getAndSet(installRunningBackendsRef, []);
@@ -740,6 +772,7 @@
 
         return yield* Effect.gen(function* () {
           yield* writeUpdateRestartMarker;
+          yield* writeUpdateInProgressMarker;
           const { downloadedVersion } = yield* Ref.get(updateStateRef);
           if (downloadedVersion !== null) yield* writeInstallRecord(downloadedVersion);
           // Stop every backend in the pool, not just the primary. With parallel
```

## apps/desktop/src/updates/updatesTestHarness.ts

Status: modified; +28; −1

```diff
@@ -27,6 +27,7 @@
   readonly setDisableDifferentialDownload?: Effect.Effect<void>;
   readonly downloadUpdate?: Effect.Effect<void>;
   readonly quitAndInstall?: Effect.Effect<void, ElectronUpdater.ElectronUpdaterQuitAndInstallError>;
+  readonly failMarkerRename?: boolean;
   readonly stopBackend?: Effect.Effect<void>;
   readonly startBackend?: Effect.Effect<void>;
   readonly backendDesiredRunning?: boolean;
@@ -214,6 +215,8 @@
       : DesktopAppSettings.layer;
 
   const updateRestartMarkers = new Set<string>();
+  const updateMarkerContents = new Map<string, string>();
+  const markerOperations: string[] = [];
   const installRecords = new Map<string, string>();
   let previousInstallRecord = options.installRecord;
   const isInstallRecord = (path: string) =>
@@ -239,11 +242,33 @@
     writeFileString: (path, data) =>
       Effect.sync(() => {
         if (isInstallRecord(path)) installRecords.set(path, data);
+        else {
+          markerOperations.push(`write ${path}`);
+          if (path.endsWith("/desktop-update-restart")) updateRestartMarkers.add(path);
+          updateMarkerContents.set(path, data);
+        }
-        else updateRestartMarkers.add(path);
       }),
+    rename: (from, to) =>
+      options.failMarkerRename
+        ? Effect.fail(
+            PlatformError.systemError({
+              module: "FileSystem",
+              method: "rename",
+              _tag: "PermissionDenied",
+              pathOrDescriptor: from,
+            }),
+          )
+        : Effect.sync(() => {
+            const contents = updateMarkerContents.get(from);
+            if (contents === undefined) throw new Error(`missing rename source: ${from}`);
+            markerOperations.push(`rename ${from} ${to}`);
+            updateMarkerContents.set(to, contents);
+            updateMarkerContents.delete(from);
+          }),
     remove: (path) =>
       Effect.sync(() => {
         updateRestartMarkers.delete(path);
+        updateMarkerContents.delete(path);
         installRecords.delete(path);
         if (isInstallRecord(path)) previousInstallRecord = undefined;
       }),
@@ -274,6 +299,8 @@
     quitAndInstalls: () => quitAndInstallCount,
     installSteps,
     updateRestartMarkers,
+    updateMarkerContents,
+    markerOperations,
     installRecords,
     previousInstallRecord: () => previousInstallRecord,
     downloadCount: () => downloadCount,
```

## apps/server/src/cli/sshHelper.test.ts

Status: added; +47; −0

```diff
@@ -1,0 +1,47 @@
+import { assert, describe, it } from "@effect/vitest";
+import * as Effect from "effect/Effect";
+import * as FileSystem from "effect/FileSystem";
+import * as TestClock from "effect/testing/TestClock";
+
+import { updateRemaining } from "./sshHelper.ts";
+
+describe("SSH update marker helper", () => {
+  it.effect.each([
+    ["fresh", '{"startedAt":0,"expiresAt":60000}', 0, "60000"],
+    ["partly elapsed", '{"startedAt":0,"expiresAt":60000}', 8000, "52000"],
+    ["shorter lifetime", '{"startedAt":0,"expiresAt":1000}', 0, "1000"],
+    ["expired", '{"startedAt":0,"expiresAt":60000}', 60000, ""],
+    ["future", '{"startedAt":1000,"expiresAt":61000}', 0, ""],
+    ["long host TTL", '{"startedAt":0,"expiresAt":600000}', 0, "60000"],
+    ["reversed", '{"startedAt":1000,"expiresAt":0}', 0, ""],
+    ["numeric strings", '{"startedAt":"0","expiresAt":"60000"}', 0, ""],
+    ["fractional", '{"startedAt":0,"expiresAt":1.5}', 0, "1.5"],
+    ["missing fields", "{}", 0, ""],
+    ["expired missing start", '{"expiresAt":0}', 0, ""],
+    ["malformed", "not json", 0, ""],
+  ] as const)("%s", ([name, contents, now, expected]) =>
+    Effect.gen(function* () {
+      let removed = false;
+      yield* TestClock.setTime(now);
+      const remaining = yield* updateRemaining("/marker").pipe(
+        Effect.provide(
+          FileSystem.layerNoop({
+            readFileString: () => Effect.succeed(contents),
+            remove: () =>
+              Effect.sync(() => {
+                removed = true;
+              }),
+          }),
+        ),
+      );
+      assert.equal(remaining, expected);
+      assert.equal(removed, ["expired", "reversed", "expired missing start"].includes(name));
+    }),
+  );
+  it.effect("missing marker and failed cleanup allow launch", () =>
+    updateRemaining("/missing").pipe(
+      Effect.provide(FileSystem.layerNoop({})),
+      Effect.tap((remaining) => Effect.sync(() => assert.equal(remaining, ""))),
+    ),
+  );
+});
```

## apps/server/src/cli/sshHelper.ts

Status: modified; +50; −1

```diff
@@ -5,7 +5,12 @@
 import * as NodeHttp from "node:http";
 import * as NodeNet from "node:net";
 
+import { DESKTOP_UPDATE_IN_PROGRESS_TTL_MS } from "@t3tools/contracts";
 import * as Effect from "effect/Effect";
+import * as Clock from "effect/Clock";
+import * as FileSystem from "effect/FileSystem";
+import * as Option from "effect/Option";
+import * as Schema from "effect/Schema";
 import { Argument, Command } from "effect/cli";
 
 const canBind = (port: number) =>
@@ -125,7 +130,51 @@
   ),
 );
 
+const UpdateMarker = Schema.fromJsonString(
+  Schema.Struct({ startedAt: Schema.optional(Schema.Unknown), expiresAt: Schema.Number }),
+);
+
+export const updateRemaining = Effect.fn("sshHelper.updateRemaining")(function* (
+  markerFile: string,
+) {
+  const fs = yield* FileSystem.FileSystem;
+  const now = yield* Clock.currentTimeMillis;
+  const marker = yield* fs
+    .readFileString(markerFile)
+    .pipe(Effect.flatMap(Schema.decodeUnknownEffect(UpdateMarker)), Effect.option);
+  if (Option.isSome(marker)) {
+    const { startedAt, expiresAt } = marker.value;
+    if (Number.isFinite(expiresAt) && expiresAt <= now) {
+      yield* fs.remove(markerFile, { force: true }).pipe(Effect.ignore);
+    } else if (
+      typeof startedAt === "number" &&
+      Number.isFinite(startedAt) &&
+      Number.isFinite(expiresAt) &&
+      startedAt <= now &&
+      expiresAt > now &&
+      expiresAt > startedAt
+    ) {
+      return String(Math.min(expiresAt - now, DESKTOP_UPDATE_IN_PROGRESS_TTL_MS));
+    }
+  }
+  return "";
+});
+
+const updateRemainingCommand = Command.make("update-remaining", {
+  markerFile: Argument.String("marker-file"),
+}).pipe(
+  Command.withHandler(({ markerFile }) =>
+    updateRemaining(markerFile).pipe(
+      Effect.flatMap((remaining) =>
+        Effect.sync(() => {
+          process.stdout.write(remaining);
+        }),
+      ),
+    ),
+  ),
+);
+
 export const sshHelperCommand = Command.make("__ssh-helper").pipe(
   Command.unlisted,
+  Command.withSubcommands([pickPort, waitReady, runtimePort, updateRemainingCommand]),
-  Command.withSubcommands([pickPort, waitReady, runtimePort]),
 );
```

## packages/contracts/src/desktopBootstrap.ts

Status: modified; +3; −0

```diff
@@ -43,3 +43,6 @@
     away, so a backend that sees a fresh marker at shutdown keeps its managed
     tunnel. */
 export const DESKTOP_UPDATE_RESTART_MARKER_FILE = "desktop-update-restart";
+
+export const DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE = "desktop-update-in-progress";
+export const DESKTOP_UPDATE_IN_PROGRESS_TTL_MS = 60_000;
```

## packages/ssh/src/launcherTestHarness.ts

Status: added; +207; −0

```diff
@@ -1,0 +1,207 @@
+// @effect-diagnostics nodeBuiltinImport:off - Executes the generated remote launcher in an isolated shell with fake commands and clock.
+import * as NodeFS from "node:fs";
+import * as NodePath from "node:path";
+import * as NodeURL from "node:url";
+import * as NodeChildProcess from "node:child_process";
+import * as NodeServices from "@effect/platform-node/NodeServices";
+import * as Effect from "effect/Effect";
+import * as Layer from "effect/Layer";
+import * as Sink from "effect/Sink";
+import * as Stream from "effect/Stream";
+import { ChildProcessSpawner } from "effect/process";
+import { launchOrReuseRemoteServer, type RemoteT3RunnerOptions } from "./tunnel.ts";
+
+// Capture exactly the script sent over SSH, then run it with fake remote commands.
+export const captureLauncherFor = Effect.fn("test.captureLauncher")(function* (
+  runner: RemoteT3RunnerOptions,
+) {
+  let script = "";
+  const spawner = ChildProcessSpawner.make((command) =>
+    Effect.gen(function* () {
+      if (command._tag === "StandardCommand") {
+        const stdin = command.options.stdin;
+        if (typeof stdin === "object" && "stream" in stdin && Stream.isStream(stdin.stream)) {
+          script = yield* stdin.stream.pipe(Stream.decodeText(), Stream.mkString);
+        }
+      }
+      return ChildProcessSpawner.makeHandle({
+        pid: ChildProcessSpawner.ProcessId(123),
+        stdout: Stream.make(new TextEncoder().encode('{"remotePort":3773}\n')),
+        stderr: Stream.empty,
+        all: Stream.empty,
+        exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
+        isRunning: Effect.succeed(false),
+        kill: () => Effect.void,
+        stdin: Sink.drain,
+        getInputFd: () => Sink.drain,
+        getOutputFd: () => Stream.empty,
+        unref: Effect.succeed(Effect.void),
+      });
+    }),
+  );
+  yield* launchOrReuseRemoteServer(
+    { alias: "test", hostname: "test.invalid", username: null, port: null },
+    undefined,
+    runner,
+  ).pipe(
+    Effect.provide(
+      Layer.merge(
+        NodeServices.layer,
+        Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
+      ),
+    ),
+  );
+  return script;
+});
+
+export const captureLauncher = captureLauncherFor({ nodeScriptPath: "/fake/server.ts" });
+
+export function runLauncher(
+  script: string,
+  options: {
+    readonly marker?: unknown;
+    readonly archive?: boolean;
+    readonly clearMarkerOnReady?: boolean;
+    readonly recordedKind?: "managed" | "external";
+    readonly clockJump?: number;
+    readonly readyAt?: number;
+    readonly initialPort?: number;
+  },
+) {
+  const root = NodeURL.fileURLToPath(new URL("../../../", import.meta.url));
+  const home = NodeFS.mkdtempSync(NodePath.join(root, "packages/ssh/.ssh-launcher-test-"));
+  try {
+    const bin = NodePath.join(home, "bin");
+    const runtime = NodePath.join(home, ".t3/runtime");
+    NodeFS.mkdirSync(bin);
+    NodeFS.mkdirSync(runtime, { recursive: true });
+    const markerPath = NodePath.join(runtime, "desktop-update-in-progress");
+    if (options.marker)
+      NodeFS.writeFileSync(
+        markerPath,
+        typeof options.marker === "string" ? options.marker : JSON.stringify(options.marker),
+      );
+    NodeFS.writeFileSync(NodePath.join(home, "clock"), "0");
+    NodeFS.writeFileSync(NodePath.join(home, "events"), "");
+    const executable = (name: string, body: string) =>
+      NodeFS.writeFileSync(NodePath.join(bin, name), `#!/bin/sh\nset -eu\n${body}\n`, {
+        mode: 0o755,
+      });
+    NodeFS.writeFileSync(
+      NodePath.join(home, "clock.cjs"),
+      `
+const fs = require('node:fs');
+Date.now = () => Number(fs.readFileSync(process.env.HOME + '/clock', 'utf8'));
+`,
+    );
+    executable(
+      options.archive ? "test-node" : "node",
+      `
+case "\${2:-}" in
+  */server-runtime.json)
+    NOW="$(cat "$HOME/clock")"
+    printf 'runtime %s\\n' "$NOW" >> "$HOME/events"
+    if [ "$NOW" -ge "$TEST_READY_AT" ]; then printf '%s %s' "$TEST_SERVER_PID" 4888
+    elif [ -n "$TEST_INITIAL_PORT" ]; then printf '%s %s' "$TEST_SERVER_PID" "$TEST_INITIAL_PORT"
+    else exit 1; fi ;;
+  */port)
+    printf 'start %s\\n' "$(cat "$HOME/clock")" >> "$HOME/events"
+    printf 5999 ;;
+  [0-9]*)
+    NOW="$(cat "$HOME/clock")"
+    printf 'probe %s %s\\n' "$2" "$NOW" >> "$HOME/events"
+    [ "$2" = 5999 ] || { [ "$NOW" -ge "$TEST_READY_AT" ] && [ "$2" = 4888 ]; } ;;
+  */desktop-update-in-progress)
+    printf 'marker %s\\n' "$(cat "$HOME/clock")" >> "$HOME/events"
+    exec "$TEST_REAL_NODE" --require "$HOME/clock.cjs" "$@" ;;
+  *) exec "$TEST_REAL_NODE" --require "$HOME/clock.cjs" "$@" ;;
+esac`,
+    );
+    executable(
+      "sleep",
+      `awk -v seconds="$1" '{ printf "%.0f", $1 + seconds * 1000 }' "$HOME/clock" > "$HOME/clock.next"
+mv "$HOME/clock.next" "$HOME/clock"
+if [ "$TEST_CLEAR_MARKER" = 1 ] && [ "$(cat "$HOME/clock")" -ge "$TEST_READY_AT" ]; then
+  rm -f "$HOME/.t3/runtime/desktop-update-in-progress"
+fi
+if [ -n "$TEST_CLOCK_JUMP" ] && [ ! -e "$HOME/jumped" ]; then
+  printf '%s' "$TEST_CLOCK_JUMP" > "$HOME/clock"
+  touch "$HOME/jumped"
+fi`,
+    );
+    executable("nohup", ":");
+    if (options.recordedKind) {
+      const state = NodePath.join(home, ".t3/ssh-launch/test-target");
+      NodeFS.mkdirSync(state, { recursive: true });
+      NodeFS.writeFileSync(NodePath.join(state, "pid"), String(process.pid));
+      NodeFS.writeFileSync(NodePath.join(state, "port"), "5999");
+      NodeFS.writeFileSync(NodePath.join(state, "managed"), options.recordedKind);
+      const runner = script.split("cat >\"$RUNNER_NEXT\" <<'SH'\n")[1]?.split("\nSH\n")[0];
+      if (!runner) throw new Error("runner missing");
+      NodeFS.writeFileSync(NodePath.join(state, "run-t3.sh"), runner + "\n");
+    }
+    if (options.archive) {
+      executable("node", 'printf "unexpected host node invocation\\n" >&2; exit 1');
+      const version = NodePath.join(home, ".t3/runtime/versions/1.2.3");
+      NodeFS.mkdirSync(version, { recursive: true });
+      NodeFS.writeFileSync(NodePath.join(version, ".install-complete"), "1.2.3");
+      NodeFS.writeFileSync(
+        NodePath.join(home, "helper.ts"),
+        `
+import * as Effect from "effect/Effect";
+import * as NodeServices from "@effect/platform-node/NodeServices";
+import { updateRemaining } from ${JSON.stringify(NodePath.join(root, "apps/server/src/cli/sshHelper.ts"))};
+process.stdout.write(await Effect.runPromise(updateRemaining(process.argv[2]).pipe(Effect.provide(NodeServices.layer))));
+`,
+      );
+      NodeFS.writeFileSync(
+        NodePath.join(version, "t3"),
+        `#!/bin/sh
+set -eu
+if [ "$1" = --version ]; then printf 1.2.3; exit; fi
+[ "$1" = __ssh-helper ]
+case "$2" in
+  update-remaining) printf 'marker %s\\n' "$(cat "$HOME/clock")" >> "$HOME/events"; exec "$TEST_REAL_NODE" --require "$HOME/clock.cjs" "$HOME/helper.ts" "$3" ;;
+  runtime-port | pick-port | wait-ready) exec "$HOME/bin/test-node" - "$3" ;;
+  *) exit 1 ;;
+esac
+`,
+        { mode: 0o755 },
+      );
+    }
+    const result = NodeChildProcess.spawnSync("/bin/sh", ["-s", "--", "test-target"], {
+      input: `kill() {
+  if [ "$1" = -0 ]; then command kill "$@"; else
+    printf 'kill %s\\n' "$*" >> "$HOME/events"
+    return 1
+  fi
+}
+${script}`,
+      encoding: "utf8",
+      timeout: 30_000,
+      env: {
+        ...process.env,
+        HOME: home,
+        PATH: `${bin}:/usr/bin:/bin`,
+        TEST_REAL_NODE: process.execPath,
+        TEST_CLEAR_MARKER: options.clearMarkerOnReady ? "1" : "0",
+        TEST_CLOCK_JUMP: options.clockJump === undefined ? "" : String(options.clockJump),
+        TEST_SERVER_PID: String(process.pid),
+        TEST_READY_AT: String(options.readyAt ?? 1_000_000),
+        TEST_INITIAL_PORT: options.initialPort === undefined ? "" : String(options.initialPort),
+      },
+    });
+    if (result.error || result.status !== 0)
+      throw new Error(
+        `launcher failed: ${result.error ?? result.status}\n${result.stderr}\n${result.stdout}\n${NodeFS.readFileSync(NodePath.join(home, "events"), "utf8")}`,
+      );
+    return {
+      output: JSON.parse(result.stdout.trim()),
+      events: NodeFS.readFileSync(NodePath.join(home, "events"), "utf8").trim().split("\n"),
+      now: Number(NodeFS.readFileSync(NodePath.join(home, "clock"), "utf8")),
+      markerExists: NodeFS.existsSync(markerPath),
+    };
+  } finally {
+    NodeFS.rmSync(home, { recursive: true, force: true });
+  }
+}
```

## packages/ssh/src/tunnel.test.ts

Status: modified; +192; −0

```diff
@@ -14,6 +14,7 @@
 import { HttpClient, HttpClientResponse } from "effect/http";
 import { ChildProcess, ChildProcessSpawner } from "effect/process";
 
+import { captureLauncher, captureLauncherFor, runLauncher } from "./launcherTestHarness.ts";
 import * as SshAuth from "./auth.ts";
 import { SshCommandError } from "./errors.ts";
 import * as SshTunnel from "./tunnel.ts";
@@ -368,3 +369,194 @@
 // desktop app with network access on) listens on *:<port>. Linux refuses that
 // bind unless both sockets set SO_REUSEPORT, so on Linux the preload adds it to
 // the picker's probe and the holder sets it too.
+
+describe("SSH launch during desktop update", () => {
+  it.effect("archive launcher reuses the replacement without host Node", () =>
+    Effect.gen(function* () {
+      const script = yield* captureLauncherFor({ archiveVersion: "1.2.3" });
+      const result = runLauncher(script, {
+        archive: true,
+        marker: { startedAt: 0, expiresAt: 60_000 },
+        readyAt: 8_000,
+      });
+      assert.deepEqual(result.output, { remotePort: 4888, serverKind: "external" });
+      assert.equal(result.now, 8_000);
+      assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+    }),
+  );
+
+  it.effect.each(["managed", "external"] as const)(
+    "reuses recorded %s server without runtime metadata or an update wait",
+    (recordedKind) =>
+      Effect.gen(function* () {
+        const result = runLauncher(yield* captureLauncher, {
+          marker: { startedAt: 0, expiresAt: 60_000 },
+          recordedKind,
+        });
+        assert.deepEqual(result.output, { remotePort: 5999, serverKind: recordedKind });
+        assert.equal(result.now, 0);
+        assert.isFalse(result.events.some((event) => event.startsWith("kill ")));
+        assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+      }),
+  );
+
+  it.effect.each([
+    ["malformed", "broken"],
+    ["future", { startedAt: 1_000, expiresAt: 61_000 }],
+    ["missing fields", {}],
+    ["numeric strings", { startedAt: "0", expiresAt: "60000" }],
+  ] as const)("preserves %s markers without waiting", ([, marker]) =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, { marker });
+      assert.equal(result.now, 0);
+      assert.isTrue(result.markerExists);
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+    }),
+  );
+
+  it.effect.each([-30_000, 120_000])("bounds a host clock jump to %s", (clockJump) =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 60_000 },
+        clockJump,
+      });
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+      assert.isBelow(result.events.filter((event) => event.startsWith("runtime ")).length, 62);
+    }),
+  );
+
+  it.effect("refreshes runtime after readiness removes the marker between polls", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 60_000 },
+        readyAt: 8_000,
+        clearMarkerOnReady: true,
+      });
+      assert.deepEqual(result.output, { remotePort: 4888, serverKind: "external" });
+      assert.equal(result.now, 8_000);
+      assert.isFalse(result.markerExists);
+      assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+      assert.isFalse(result.events.some((event) => event.startsWith("kill ")));
+    }),
+  );
+
+  it.effect("waits across the eight-second installer gap and reuses the replacement server", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 60_000 },
+        readyAt: 8_000,
+      });
+      assert.deepEqual(
+        result.output,
+        { remotePort: 4888, serverKind: "external" },
+        `replacement appears at 8000ms; launcher returned at ${result.now}ms: ${result.events.join(", ")}`,
+      );
+      assert.equal(result.now, 8_000);
+      assert.isBelow(result.now, 60_000);
+      assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+      assert.isAtLeast(result.events.filter((event) => event.startsWith("runtime ")).length, 2);
+    }),
+  );
+
+  it.effect(
+    "refreshes the runtime port while waiting instead of probing the old port forever",
+    () =>
+      Effect.gen(function* () {
+        const result = runLauncher(yield* captureLauncher, {
+          marker: { startedAt: 0, expiresAt: 60_000 },
+          initialPort: 3773,
+          readyAt: 8_000,
+        });
+        assert.deepEqual(result.output, { remotePort: 4888, serverKind: "external" });
+        assert.isTrue(result.events.some((event) => event.startsWith("probe 4888 ")));
+        assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+      }),
+  );
+
+  it.effect("waits the full marker lifetime despite immediately failing stale-port probes", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 60_000 },
+        initialPort: 3773,
+        readyAt: 30_000,
+      });
+      assert.deepEqual(result.output, { remotePort: 4888, serverKind: "external" });
+      assert.equal(result.now, 30_000);
+      assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+    }),
+  );
+
+  it.effect("starts a server when the marker expires without a replacement becoming ready", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 60_000 },
+      });
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+      assert.isAtLeast(result.now, 60_000);
+      assert.isAtMost(result.now, 61_000);
+      assert.isFalse(result.markerExists, "an expired marker must be removed");
+      assert.equal(result.events.filter((event) => event.startsWith("start ")).length, 1);
+    }),
+  );
+
+  it.effect("honours the explicit expiresAt rather than waiting a new 60-second window", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 1_000 },
+      });
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+      assert.isAtLeast(result.now, 1_000);
+      assert.isAtMost(result.now, 2_000);
+      assert.isFalse(result.markerExists);
+    }),
+  );
+
+  it.effect("ignores and removes a stale marker without waiting", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: -60_000, expiresAt: 0 },
+      });
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+      assert.equal(result.now, 0);
+      assert.isFalse(result.markerExists);
+    }),
+  );
+
+  it.effect.each([false, true])("starts immediately without a marker (archive: %s)", (archive) =>
+    Effect.gen(function* () {
+      const script = archive
+        ? yield* captureLauncherFor({ archiveVersion: "1.2.3" })
+        : yield* captureLauncher;
+      const result = runLauncher(script, { archive });
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+      assert.equal(result.now, 0);
+      assert.equal(result.events.filter((event) => event.startsWith("start ")).length, 1);
+      assert.equal(result.events.filter((event) => event.startsWith("marker ")).length, 1);
+      assert.equal(result.events.filter((event) => event.startsWith("runtime ")).length, 1);
+    }),
+  );
+
+  it.effect("caps a longer host TTL and preserves its marker", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 600_000 },
+      });
+      assert.deepEqual(result.output, { remotePort: 5999, serverKind: "managed" });
+      assert.equal(result.now, 60_000);
+      assert.isTrue(result.markerExists);
+      assert.equal(result.events.filter((event) => event.startsWith("marker ")).length, 61);
+    }),
+  );
+
+  it.effect("reuses an already ready server without waiting for a fresh marker to expire", () =>
+    Effect.gen(function* () {
+      const result = runLauncher(yield* captureLauncher, {
+        marker: { startedAt: 0, expiresAt: 60_000 },
+        readyAt: 0,
+      });
+      assert.deepEqual(result.output, { remotePort: 4888, serverKind: "external" });
+      assert.equal(result.now, 0);
+      assert.isFalse(result.events.some((event) => event.startsWith("start ")));
+    }),
+  );
+});
```

## packages/ssh/src/tunnel.ts

Status: modified; +59; −1

```diff
@@ -1,3 +1,7 @@
+import {
+  DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE,
+  DESKTOP_UPDATE_IN_PROGRESS_TTL_MS,
+} from "@t3tools/contracts";
 import type {
   DesktopSshEnvironmentBootstrap,
   DesktopSshEnvironmentTarget,
@@ -50,7 +54,8 @@
 const TUNNEL_SHUTDOWN_TIMEOUT_MS = 2_000;
 const TUNNEL_STDERR_TAIL_CHARS = 16_384;
 const REMOTE_READY_TIMEOUT_MS = 60_000;
-const REMOTE_LAUNCH_TIMEOUT_MS = 90_000;
+const REMOTE_LAUNCH_TIMEOUT_MS =
+  DESKTOP_UPDATE_IN_PROGRESS_TTL_MS + REMOTE_READY_TIMEOUT_MS + 30_000;
 // A cold archive launch also downloads and unpacks a ~70 MB release archive
 // and may wait on another installer's lock. The budgets nest: the checksum
 // file is tiny and the archive download is bounded; a waiter outlasts both
@@ -630,9 +635,31 @@
 }
 NODE
 }
+# This script ships with the SSH client; older clients do not honor this marker.
+update_remaining() {
+  if [ "$T3_ARCHIVE_MODE" = "1" ]; then
+    "$RUNNER_FILE" __ssh-helper update-remaining "$DEFAULT_SERVER_HOME/runtime/@@T3_UPDATE_MARKER_FILE@@"
+    return
+  fi
+  node - "$DEFAULT_SERVER_HOME/runtime/@@T3_UPDATE_MARKER_FILE@@" <<'NODE'
+const fs = require("node:fs");
+const path = process.argv[2];
+try {
+  const { startedAt, expiresAt } = JSON.parse(fs.readFileSync(path, "utf8"));
+  const now = Date.now();
+  if (Number.isFinite(startedAt) && Number.isFinite(expiresAt) &&
+      startedAt <= now && expiresAt > now && expiresAt > startedAt) {
+    process.stdout.write(String(Math.min(expiresAt - now, @@T3_UPDATE_TTL_MS@@)));
+  } else if (Number.isFinite(expiresAt) && expiresAt <= now) {
+    fs.rmSync(path, { force: true });
+  }
+} catch {}
+NODE
+}
 REMOTE_PID="$(cat "$PID_FILE" 2>/dev/null || true)"
 REMOTE_PORT="$(cat "$PORT_FILE" 2>/dev/null || true)"
 REMOTE_MANAGED="$(cat "$MANAGED_FILE" 2>/dev/null || true)"
+adopt_default_server() {
 DEFAULT_RUNTIME_INFO="$(resolve_default_runtime_port 2>/dev/null || true)"
 DEFAULT_RUNTIME_PID=""
 DEFAULT_REMOTE_PORT=""
@@ -647,6 +674,7 @@
 if [ -n "$DEFAULT_REMOTE_PORT" ] && [ "$REMOTE_MANAGED" = "managed" ] && [ "$DEFAULT_RUNTIME_PID" = "$REMOTE_PID" ]; then
   REMOTE_PORT="$DEFAULT_REMOTE_PORT"
   printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE"
+  return 0
 elif [ -n "$DEFAULT_REMOTE_PORT" ]; then
   REMOTE_PORT="$DEFAULT_REMOTE_PORT"
   if wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then
@@ -668,12 +696,16 @@
       REMOTE_PID=""
       REMOTE_MANAGED="external"
     fi
+    return 0
   else
     REMOTE_PID="$(cat "$PID_FILE" 2>/dev/null || true)"
     REMOTE_PORT="$(cat "$PORT_FILE" 2>/dev/null || true)"
     REMOTE_MANAGED="$(cat "$MANAGED_FILE" 2>/dev/null || true)"
   fi
 fi
+return 1
+}
+adopt_default_server || true
 if [ "$REMOTE_MANAGED" = "external" ]; then
   # A server adopted above has just answered. While it is still alive, probing
   # it again could catch a momentary stall and launch a second server beside it.
@@ -703,6 +735,29 @@
   REMOTE_MANAGED=""
 fi
 if [ -z "$REMOTE_PORT" ]; then
+  # The sleep count bounds a host clock that stops advancing.
+  UPDATE_WAITED=0
+  UPDATE_SAW_MARKER=0
+  while [ "$UPDATE_WAITED" -le @@T3_UPDATE_TTL_SECONDS@@ ]; do
+    UPDATE_REMAINING="$(update_remaining 2>/dev/null || true)"
+    [ -n "$UPDATE_REMAINING" ] || break
+    UPDATE_SAW_MARKER=1
+    [ "$UPDATE_WAITED" -lt @@T3_UPDATE_TTL_SECONDS@@ ] || break
+    if adopt_default_server; then break; fi
+    REMOTE_PID=""
+    REMOTE_PORT=""
+    REMOTE_MANAGED=""
+    sleep 1
+    UPDATE_WAITED=$((UPDATE_WAITED + 1))
+  done
+  # Readiness clears the marker, so refresh once more before falling back.
+  if [ "$UPDATE_SAW_MARKER" -eq 1 ] && [ -z "$REMOTE_PORT" ] && ! adopt_default_server; then
+    REMOTE_PID=""
+    REMOTE_PORT=""
+    REMOTE_MANAGED=""
+  fi
+fi
+if [ -z "$REMOTE_PORT" ]; then
   REMOTE_PORT="$(pick_port)" || true
   if [ -z "$REMOTE_PORT" ]; then
     if [ "$T3_ARCHIVE_MODE" = "1" ]; then
@@ -838,6 +893,9 @@
 
 function buildRemoteLaunchScript(input?: RemoteT3RunnerOptions): string {
   return applyScriptPlaceholders(REMOTE_LAUNCH_SCRIPT, {
+    T3_UPDATE_MARKER_FILE: DESKTOP_UPDATE_IN_PROGRESS_MARKER_FILE,
+    T3_UPDATE_TTL_MS: String(DESKTOP_UPDATE_IN_PROGRESS_TTL_MS),
+    T3_UPDATE_TTL_SECONDS: String(DESKTOP_UPDATE_IN_PROGRESS_TTL_MS / 1000),
     T3_ARCHIVE_MODE: isNodeScriptRunner(input) ? "0" : "1",
     T3_NODE_ENV_SCRIPT: buildRemoteNodeEnvScript(input),
     T3_RUNNER_SCRIPT: stripTrailingNewlines(buildRemoteT3RunnerScript(input)),
```

## scripts/evidence/pr-23005-ssh-update-race.sh

Status: added; +375; −0

```diff
@@ -1,0 +1,375 @@
+#!/usr/bin/env bash
+# Evidence driver for PR #23005 (issue #22990): runs the real SSH launch
+# script against a throwaway HOME, with real servers, for one fixture row.
+#
+#   scripts/evidence/pr-23005-ssh-update-race.sh <checkout> <row> [workdir]
+#
+# <checkout> is the tree under test (before or after the fix); the launch
+# script is generated from its own packages/ssh/src/tunnel.ts. Rows: F1 F2
+# F2b F3 F3b F3c F4 F5 F6 F7 F8 F8-archive F8b desktop-warmup F13 F14.
+# F8b also needs OLD_CHECKOUT=<pre-fix checkout> for the remote t3 that lacks
+# update-remaining. F13/F14 run the dev desktop (`vp run dev:desktop`) under
+# headless-desktop with --home-dir $FAKE/.t3 and an isolated
+# XDG_CONFIG_HOME; run desktop-warmup first so the build does not eat the
+# marker window. Stills land in $WORK/shots.
+#
+# The launcher's readiness probe wants GET / to answer 2xx, so the checkout
+# needs apps/web/dist/index.html (a real web build or a one-line stub).
+#
+# Safety: every HOME is a fresh mktemp dir under the workdir, never the real
+# one, and only PIDs recorded at spawn or in $FAKE/.t3/ssh-launch/k1/pid are
+# killed.
+set -euo pipefail
+
+CO="$(cd "${1:?checkout}" && pwd)"
+ROW="${2:?row}"
+WORK="${3:-/tmp/ev-23005/driver}"
+HERE="$(cd "$(dirname "$0")" && pwd)"
+ARCHIVE_VERSION="9.9.9"
+MARKER_REL=".t3/runtime/desktop-update-in-progress"
+mkdir -p "$WORK"
+# Version-manager shims resolve node under $HOME; pin the real binary so the
+# throwaway HOMEs do not each install a runtime.
+PATH="$(dirname "$(node -p process.execPath)"):$PATH"
+export PATH
+
+ts() { date +%T.%3N; }
+say() { printf '[%s] %s\n' "$(ts)" "$*"; }
+
+capture_scripts() {
+  local helper="$CO/packages/ssh/src/.ev-capture-launcher.ts"
+  cat >"$helper" <<'EV_HELPER'
+// @effect-diagnostics nodeBuiltinImport:off - Evidence helper; copied next to tunnel.ts in the checkout under test.
+// Prints the exact launch script launchOrReuseRemoteServer sends over SSH.
+// Uses only the public launchOrReuseRemoteServer so it runs unchanged on
+// checkouts that predate launcherTestHarness.ts.
+// Usage: node <checkout>/packages/ssh/src/<this file> <out.sh> node <bin.ts> | archive <version>
+// Writes to a file because the Effect logger prints to stdout.
+import * as NodeServices from "@effect/platform-node/NodeServices";
+import * as NodeFS from "node:fs";
+import * as Effect from "effect/Effect";
+import * as Layer from "effect/Layer";
+import * as Sink from "effect/Sink";
+import * as Stream from "effect/Stream";
+import { ChildProcessSpawner } from "effect/process";
+import { launchOrReuseRemoteServer } from "./tunnel.ts";
+
+const [out, mode, value] = process.argv.slice(2);
+if (!out || (mode !== "node" && mode !== "archive") || !value) {
+  console.error("usage: <out.sh> node <bin.ts> | archive <version>");
+  process.exit(2);
+}
+
+let script = "";
+const spawner = ChildProcessSpawner.make((command) =>
+  Effect.gen(function* () {
+    if (command._tag === "StandardCommand") {
+      const stdin = command.options.stdin;
+      if (typeof stdin === "object" && "stream" in stdin && Stream.isStream(stdin.stream)) {
+        script = yield* stdin.stream.pipe(Stream.decodeText(), Stream.mkString);
+      }
+    }
+    return ChildProcessSpawner.makeHandle({
+      pid: ChildProcessSpawner.ProcessId(123),
+      stdout: Stream.make(new TextEncoder().encode('{"remotePort":3773}\n')),
+      stderr: Stream.empty,
+      all: Stream.empty,
+      exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
+      isRunning: Effect.succeed(false),
+      kill: () => Effect.void,
+      stdin: Sink.drain,
+      getInputFd: () => Sink.drain,
+      getOutputFd: () => Stream.empty,
+      unref: Effect.succeed(Effect.void),
+    });
+  }),
+);
+
+await Effect.runPromise(
+  launchOrReuseRemoteServer(
+    { alias: "evidence", hostname: "evidence.invalid", username: null, port: null },
+    undefined,
+    mode === "node" ? { nodeScriptPath: value } : { archiveVersion: value },
+  ).pipe(
+    Effect.provide(
+      Layer.merge(
+        NodeServices.layer,
+        Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
+      ),
+    ),
+  ),
+);
+NodeFS.writeFileSync(out, script);
+EV_HELPER
+  node "$helper" "$WORK/launch-node.sh" node "$CO/apps/server/src/bin.ts" >/dev/null 2>&1
+  node "$helper" "$WORK/launch-archive.sh" archive "$ARCHIVE_VERSION" >/dev/null 2>&1
+  rm -f "$helper"
+}
+
+STARTED_PIDS=()
+FAKE=""
+cleanup() {
+  local pid
+  if [ -n "$FAKE" ] && [ -f "$FAKE/.t3/ssh-launch/k1/pid" ]; then
+    STARTED_PIDS+=("$(cat "$FAKE/.t3/ssh-launch/k1/pid")")
+  fi
+  if [ -n "${DESKTOP_PGID:-}" ]; then
+    kill -TERM -- "-$DESKTOP_PGID" 2>/dev/null || true
+    while kill -0 -- "-$DESKTOP_PGID" 2>/dev/null; do sleep 0.2; done
+    DESKTOP_PGID=""
+  fi
+  for pid in "${STARTED_PIDS[@]}"; do
+    kill "$pid" 2>/dev/null || true
+  done
+  for pid in "${STARTED_PIDS[@]}"; do
+    while kill -0 "$pid" 2>/dev/null; do sleep 0.1; done
+  done
+  STARTED_PIDS=()
+}
+trap cleanup EXIT
+
+new_home() {
+  cleanup
+  FAKE="$(mktemp -d "$WORK/home.XXXX")"
+  mkdir -p "$FAKE/.t3/runtime"
+  say "fresh HOME=$FAKE"
+}
+
+# write_marker <startedAt offset ms> <expiresAt offset ms> | write_raw <text>
+write_marker() {
+  node -e 'const n=Date.now();process.stdout.write(JSON.stringify({startedAt:n+Number(process.argv[1]),expiresAt:n+Number(process.argv[2])}))' \
+    -- "$1" "$2" >"$FAKE/$MARKER_REL"
+  say "marker written: $(cat "$FAKE/$MARKER_REL") (now=$(node -p 'Date.now()'))"
+}
+write_raw() {
+  printf '%s' "$1" >"$FAKE/$MARKER_REL"
+  say "marker written: $1"
+}
+show_marker() {
+  if [ -e "$FAKE/$MARKER_REL" ]; then say "marker PRESENT: $(cat "$FAKE/$MARKER_REL")"; else say "marker ABSENT"; fi
+}
+
+free_port() {
+  node -e 'const s=require("net").createServer();s.listen(0,"127.0.0.1",()=>{console.log(s.address().port);s.close()})'
+}
+
+# Stands in for the updated desktop backend: a real server on the default home.
+start_replacement() {
+  local port
+  port="$(free_port)"
+  HOME="$FAKE" T3CODE_NO_BROWSER=1 nohup node "$CO/apps/server/src/bin.ts" serve \
+    --host 127.0.0.1 --port "$port" --base-dir "$FAKE/.t3" >"$FAKE/replacement.log" 2>&1 </dev/null &
+  REPLACEMENT_PID=$!
+  STARTED_PIDS+=("$REPLACEMENT_PID")
+  say "replacement server spawned pid=$REPLACEMENT_PID port=$port"
+}
+runtime_json() { tr -d '\n' <"$FAKE/.t3/userdata/server-runtime.json" 2>/dev/null | cut -c1-120; }
+# The launcher's own server shares this base dir, so wait for the file to name
+# the replacement's PID, not just to exist.
+wait_runtime_file() {
+  local i=0
+  while ! grep -q "\"pid\":$REPLACEMENT_PID," "$FAKE/.t3/userdata/server-runtime.json" 2>/dev/null; do
+    i=$((i + 1))
+    if [ "$i" -ge 300 ]; then
+      say "replacement did not publish server-runtime.json within 30 s; file now: $(runtime_json)"
+      return 0
+    fi
+    sleep 0.1
+  done
+  say "replacement (pid $REPLACEMENT_PID) wrote server-runtime.json: $(runtime_json)"
+}
+
+seed_archive() {
+  local bin="$1" dir="$FAKE/.t3/runtime/versions/$ARCHIVE_VERSION"
+  mkdir -p "$dir"
+  printf '#!/bin/sh\nexec node %s "$@"\n' "$bin" >"$dir/t3"
+  chmod 755 "$dir/t3"
+  printf '%s\n' "$ARCHIVE_VERSION" >"$dir/.install-complete"
+  say "seeded archive runtime $ARCHIVE_VERSION -> node $bin"
+}
+
+LAUNCH_T0=0
+launch_bg() {
+  local mode="${1:-node}"
+  LAUNCH_T0=$(date +%s%3N)
+  say "launcher START ($mode mode)"
+  (
+    rc=0
+    HOME="$FAKE" sh -l -s -- k1 <"$WORK/launch-$mode.sh" >"$FAKE/launch.out" 2>"$FAKE/launch.err" || rc=$?
+    printf '%s %s %s\n' "$rc" "$(date +%s%3N)" "$(date +%T.%3N)" >"$FAKE/launch.end"
+  ) &
+  LAUNCH_PID=$!
+}
+launch_wait() {
+  local rc end_ms end_ts
+  wait "$LAUNCH_PID" || true
+  read -r rc end_ms end_ts <"$FAKE/launch.end"
+  say "launcher EXIT rc=$rc at $end_ts, $(( end_ms - LAUNCH_T0 )) ms after START; stdout: $(tail -n1 "$FAKE/launch.out")"
+  if [ "$rc" -ne 0 ]; then say "launcher stderr: $(grep -v '[▀█▄]' "$FAKE/launch.err" | tail -n3 | tr '\n' ' ')"; fi
+  report_servers
+}
+launch_fg() { launch_bg "${1:-node}"; launch_wait; }
+
+report_servers() {
+  local f="$FAKE/.t3/ssh-launch/k1"
+  say "state: managed=$(cat "$f/managed" 2>/dev/null || echo -) pid=$(cat "$f/pid" 2>/dev/null || echo -) port=$(cat "$f/port" 2>/dev/null || echo -)"
+  say "server processes on this HOME ($(ps -eo pid=,args= | grep -F -- "--base-dir $FAKE/.t3" | grep -v grep | wc -l)):"
+  ps -eo pid=,args= | grep -F -- "--base-dir $FAKE/.t3" | grep -v grep | sed "s|^|    |" || true
+  show_marker
+}
+
+# Starts the dev desktop with its data dir at $FAKE/.t3, in its own session
+# so it stops by the process group captured here.
+DESKTOP_PGID=""
+start_desktop() {
+  local cfg="$WORK/xdg-config"
+  mkdir -p "$cfg" "$WORK/shots"
+  rm -f "$WORK/display"
+  ( cd "$CO" && XDG_CONFIG_HOME="$cfg" exec setsid headless-desktop bash -c \
+      'printf "%s %s\n" "$DISPLAY" "$XAUTHORITY" >"$0"; exec vp run dev:desktop --home-dir "$1"' \
+      "$WORK/display" "$FAKE/.t3" >"$FAKE/desktop.log" 2>&1 ) &
+  DESKTOP_PGID=$!
+  say "desktop spawned (pgid=$DESKTOP_PGID, --home-dir $FAKE/.t3, XDG_CONFIG_HOME=$cfg)"
+  while [ ! -s "$WORK/display" ]; do sleep 0.2; done
+}
+shot() {
+  local display xauth
+  read -r display xauth <"$WORK/display"
+  XAUTHORITY="$xauth" import -display "$display" -window root "$WORK/shots/$1.png" 2>/dev/null &&
+    say "still: $WORK/shots/$1.png"
+}
+backend_log_lines() {
+  grep -E 'backend start requested|Listening on|backend ready|main window created' "$FAKE/desktop.log" 2>/dev/null | sed 's/^/    /' || true
+}
+# Polls until the dev desktop's backend has written server-runtime.json and the
+# window had a moment to paint, printing the marker state every 0.5 s.
+watch_desktop() {
+  local limit="$1" t0 n=0 ready_at=""
+  t0=$(date +%s)
+  while [ $(( $(date +%s) - t0 )) -lt "$limit" ]; do
+    if [ -z "$ready_at" ] && [ -s "$FAKE/.t3/userdata/server-runtime.json" ]; then ready_at=$(date +%s); fi
+    if [ -n "$ready_at" ] && [ $(( $(date +%s) - ready_at )) -ge 8 ]; then break; fi
+    if [ $((n % 4)) -eq 0 ]; then
+      say "poll: marker $([ -e "$FAKE/$MARKER_REL" ] && echo present || echo ABSENT); runtime.json $([ -s "$FAKE/.t3/userdata/server-runtime.json" ] && echo present || echo absent)"
+    fi
+    n=$((n + 1))
+    sleep 0.5
+  done
+}
+
+say "checkout=$CO head=$(git -C "$CO" rev-parse --short=10 HEAD) row=$ROW node=$(node -v) $(uname -sr)"
+capture_scripts
+say "launch scripts: node $(wc -l <"$WORK/launch-node.sh") lines, update_remaining refs: $(grep -c update_remaining "$WORK/launch-node.sh" || true)"
+
+case "$ROW" in
+  F1)
+    new_home; show_marker; launch_fg node
+    new_home; seed_archive "$CO/apps/server/src/bin.ts"; show_marker; launch_fg archive
+    ;;
+  F2)
+    new_home; write_marker 0 60000
+    launch_bg node
+    sleep 8; say "8 s elapsed: desktop replacement backend comes up"
+    start_replacement; wait_runtime_file
+    launch_wait
+    ;;
+  F2b)
+    new_home; write_marker 0 60000
+    launch_bg node
+    sleep 8; start_replacement; wait_runtime_file
+    rm -f "$FAKE/$MARKER_REL"; say "backend ready: marker removed (as DesktopBackendPool onReady does)"
+    launch_wait
+    ;;
+  F3)
+    new_home; write_marker 0 60000
+    launch_fg node
+    ;;
+  F3b)
+    new_home; write_marker 0 1000
+    launch_fg node
+    ;;
+  F3c)
+    new_home; write_marker 0 600000
+    launch_fg node
+    ;;
+  F4)
+    new_home; write_marker -120000 -60000
+    launch_fg node
+    ;;
+  F5)
+    for variant in broken future empty numeric; do
+      new_home
+      case "$variant" in
+        broken) write_raw 'broken' ;;
+        future) write_marker 60000 120000 ;;
+        empty) write_raw '{}' ;;
+        numeric) write_raw '{"startedAt":"0","expiresAt":"99999999999999"}' ;;
+      esac
+      launch_fg node
+    done
+    ;;
+  F6)
+    new_home; start_replacement; wait_runtime_file; sleep 3
+    write_marker 0 60000
+    launch_fg node
+    ;;
+  F7)
+    new_home; say "first launch: starts a managed server"; launch_fg node
+    say "second launch, no marker: reuses the recorded managed server"; launch_fg node
+    ;;
+  F8)
+    m="$WORK/m.json"
+    for variant in fresh partial expired future malformed missing; do
+      rm -f "$m"
+      now=$(node -p 'Date.now()')
+      case "$variant" in
+        fresh) printf '{"startedAt":%s,"expiresAt":%s}' "$now" $((now + 60000)) >"$m" ;;
+        partial) printf '{"startedAt":%s,"expiresAt":%s}' $((now - 45000)) $((now + 15000)) >"$m" ;;
+        expired) printf '{"startedAt":%s,"expiresAt":%s}' $((now - 120000)) $((now - 60000)) >"$m" ;;
+        future) printf '{"startedAt":%s,"expiresAt":%s}' $((now + 60000)) $((now + 120000)) >"$m" ;;
+        malformed) printf 'broken' >"$m" ;;
+        missing) : ;;
+      esac
+      say "variant=$variant marker=$(cat "$m" 2>/dev/null || echo '<none>')"
+      rc=0; out=$(node "$CO/apps/server/src/bin.ts" __ssh-helper update-remaining "$m" 2>&1) || rc=$?
+      say "  rc=$rc output=[$(printf '%s' "$out" | tr '\n' ' ' | cut -c1-200)] file-after=$([ -e "$m" ] && echo present || echo absent)"
+    done
+    ;;
+  F8-archive)
+    new_home; seed_archive "$CO/apps/server/src/bin.ts"; write_marker 0 3000
+    launch_fg archive
+    ;;
+  F8b)
+    : "${OLD_CHECKOUT:?OLD_CHECKOUT must point at a pre-fix checkout}"
+    new_home; seed_archive "$(cd "$OLD_CHECKOUT" && pwd)/apps/server/src/bin.ts"; write_marker 0 60000
+    launch_fg archive
+    ;;
+  desktop-warmup)
+    new_home; start_desktop
+    while [ ! -s "$FAKE/.t3/userdata/server-runtime.json" ]; do sleep 1; done
+    say "desktop backend wrote server-runtime.json; warm-up done"
+    sleep 5; shot warmup
+    ;;
+  F13)
+    new_home; write_marker 0 600000
+    show_marker
+    start_desktop
+    watch_desktop 150
+    shot F13
+    say "desktop log lines about backend readiness:"; backend_log_lines
+    show_marker
+    ;;
+  F14)
+    new_home; write_marker 0 60000
+    say "desktop backend is down for the update (marker written); remote client connects now"
+    launch_bg node
+    sleep 1; start_desktop
+    launch_wait
+    say "desktop runtime: $(tr -d '\n' <"$FAKE/.t3/userdata/server-runtime.json" 2>/dev/null | cut -c1-200)"
+    sleep 5; shot F14
+    say "desktop log lines about backend readiness:"; backend_log_lines
+    ;;
+  *)
+    echo "unknown row $ROW" >&2; exit 2 ;;
+esac
+say "row $ROW done"
```
