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

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

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

Visibility: public

[fix\(desktop\): check the backend's launch id before sending the bootstrap token](https://git.cafe/pingdotgg/t3code/pull/21102.md)

Version: 8

Head commit: b80f11c49c0920b99f2127f831ac187f25faa796

Base commit: 680152e27b2a961686389ca69a1557cbb2c55f08

Comparison base: 680152e27b2a961686389ca69a1557cbb2c55f08

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

## apps/desktop/src/app/DesktopApp.ts

Status: modified; +4; −40

```diff
@@ -5,7 +5,6 @@
 import * as Ref from "effect/Ref";
 import * as Schema from "effect/Schema";
 
-import * as NetService from "@t3tools/shared/Net";
 import * as Crypto from "effect/Crypto";
 import * as ElectronApp from "../electron/ElectronApp.ts";
 import * as ElectronDialog from "../electron/ElectronDialog.ts";
@@ -38,27 +37,12 @@
 import * as HostProcess from "@t3tools/shared/HostProcess";
 
 const DEFAULT_DESKTOP_BACKEND_PORT = 3773;
-const MAX_TCP_PORT = 65_535;
-const DESKTOP_BACKEND_PORT_PROBE_HOSTS = ["127.0.0.1", "0.0.0.0", "::"] as const;
 
 const makeDesktopRunId = Crypto.Crypto.pipe(
   Effect.flatMap((crypto) => crypto.randomUUIDv4()),
   Effect.map((value) => value.replaceAll("-", "").slice(0, 12)),
 );
 
-export class DesktopBackendPortUnavailableError extends Schema.TaggedError<DesktopBackendPortUnavailableError>()(
-  "DesktopBackendPortUnavailableError",
-  {
-    startPort: Schema.Int,
-    maxPort: Schema.Int,
-    hosts: Schema.Array(Schema.String),
-  },
-) {
-  override get message(): string {
-    return `No desktop backend port is available on hosts ${this.hosts.join(", ")} between ${this.startPort} and ${this.maxPort}.`;
-  }
-}
-
 export class DesktopDevelopmentBackendPortRequiredError extends Schema.TaggedError<DesktopDevelopmentBackendPortRequiredError>()(
   "DesktopDevelopmentBackendPortRequiredError",
   {},
@@ -84,30 +68,10 @@
     } as const;
   }
 
-  const net = yield* NetService.NetService;
-  for (let port = DEFAULT_DESKTOP_BACKEND_PORT; port <= MAX_TCP_PORT; port += 1) {
-    let availableOnEveryHost = true;
-
-    for (const host of DESKTOP_BACKEND_PORT_PROBE_HOSTS) {
-      if (!(yield* net.canListenOnHost(port, host))) {
-        availableOnEveryHost = false;
-        break;
-      }
-    }
-
-    if (availableOnEveryHost) {
-      return {
-        port,
-        selectedByScan: true,
-      } as const;
-    }
-  }
+  return {
-
+    port: yield* DesktopServerExposure.findAvailableBackendPort(DEFAULT_DESKTOP_BACKEND_PORT),
+    selectedByScan: true,
+  } as const;
-  return yield* new DesktopBackendPortUnavailableError({
-    startPort: DEFAULT_DESKTOP_BACKEND_PORT,
-    maxPort: MAX_TCP_PORT,
-    hosts: DESKTOP_BACKEND_PORT_PROBE_HOSTS,
-  });
 });
 
 const handleFatalStartupError = Effect.fn("desktop.startup.handleFatalStartupError")(function* (
```

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

Status: modified; +4; −0

```diff
@@ -243,6 +243,7 @@
 interface SharedBootstrapInput {
   readonly bootstrapToken: string;
   readonly bootstrapSecret: string;
+  readonly launchId: string;
   readonly observabilitySettings: BackendObservabilitySettings;
 }
 
@@ -520,6 +521,7 @@
       host: backendExposure.bindHost,
       desktopBootstrapToken: input.bootstrapToken,
       desktopBootstrapSecret: input.bootstrapSecret,
+      desktopLaunchId: input.launchId,
       // Failed Windows probes leave server hydration enabled for another attempt.
       shellEnvironmentPrepared: input.shellEnvironmentPrepared,
       tailscaleServeEnabled: backendExposure.tailscaleServeEnabled,
@@ -598,6 +600,7 @@
     host: wslBindHost,
     desktopBootstrapToken: input.bootstrapToken,
     desktopBootstrapSecret: input.bootstrapSecret,
+    desktopLaunchId: input.launchId,
     // PortSchema rejects 0, so when tailscale serve is disabled we still need a
     // valid number in this slot.
     tailscaleServeEnabled: false,
@@ -790,6 +793,7 @@
     return {
       bootstrapToken,
       bootstrapSecret,
+      launchId: Hex.encode(yield* crypto.randomBytes(16)),
       observabilitySettings,
     } satisfies SharedBootstrapInput;
   });
```

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

Status: modified; +176; −4

```diff
@@ -1,7 +1,6 @@
 import {
   SERVER_EXIT_CODE_STATE_DIR_OWNED,
   DesktopBackendBootstrap,
-  type DesktopBackendBootstrap as DesktopBackendBootstrapValue,
   DesktopTelemetryControlMessage,
 } from "@t3tools/contracts";
 import { assert, describe, it } from "@effect/vitest";
@@ -49,6 +48,7 @@
     t3Home: "/tmp/t3",
     host: "127.0.0.1",
     desktopBootstrapToken: "token",
+    desktopLaunchId: "launch-1",
     tailscaleServeEnabled: false,
     tailscaleServePort: 443,
     desktopTelemetryFd: 4,
@@ -61,7 +61,7 @@
   preflightFailure: Option.none(),
 };
 
-const configWithObservability: DesktopBackendBootstrapValue = {
+const configWithObservability: DesktopBackendManager.DesktopBackendStartConfig["bootstrap"] = {
   ...baseConfig.bootstrap,
   tailscaleServeEnabled: true,
   desktopTelemetryFd: 4,
@@ -94,7 +94,10 @@
   request: HttpClientRequest.HttpClientRequest,
   status: number,
 ): HttpClientResponse.HttpClientResponse {
+  return HttpClientResponse.fromWeb(
-  return HttpClientResponse.fromWeb(request, new Response(null, { status }));
+    request,
+    new Response(status < 300 ? '{"desktopLaunchId":"launch-1"}' : null, { status }),
+  );
 }
 
 function layerHttpClient(
@@ -126,6 +129,7 @@
   readonly onPreflightFailed?: (
     failure: DesktopBackendManager.PreflightFailure,
   ) => Effect.Effect<boolean>;
+  readonly onPortTaken?: (port: number) => Effect.Effect<boolean>;
   readonly config?: DesktopBackendManager.DesktopBackendStartConfig;
   readonly configResolve?: Effect.Effect<
     DesktopBackendManager.DesktopBackendStartConfig,
@@ -182,6 +186,7 @@
     ...(input.onShutdown ? { onShutdown: () => input.onShutdown! } : {}),
     ...(input.onStateDirOwned ? { onStateDirOwned: () => input.onStateDirOwned! } : {}),
     ...(input.onPreflightFailed ? { onPreflightFailed: input.onPreflightFailed } : {}),
+    ...(input.onPortTaken ? { onPortTaken: input.onPortTaken } : {}),
   });
 
   return instance.pipe(Effect.provide(layerServices));
@@ -291,7 +296,11 @@
         desktopTelemetryStream: Stream.empty,
       }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)));
 
+      assert.deepEqual(exit, {
+        code: Option.none(),
+        reason: "killed by SIGKILL",
-      assert.deepEqual(exit, { code: Option.none(), reason: "killed by SIGKILL" });
+        portTaken: false,
+      });
     }),
   );
 
@@ -373,6 +382,169 @@
     ),
   );
 
+  it.effect.each([401, 404])(
+    "stops its backend when a persistent HTTP %s responder occupies its port",
+    (status) =>
+      Effect.scoped(
+        Effect.gen(function* () {
+          const killed = yield* Deferred.make<void>();
+          const firstRequest = yield* Deferred.make<void>();
+          let readyCount = 0;
+          const layerSpawner = Layer.succeed(
+            ChildProcessSpawner.ChildProcessSpawner,
+            ChildProcessSpawner.make(() =>
+              Effect.succeed(
+                makeProcess({
+                  exitCode: Deferred.await(killed).pipe(Effect.as(ChildProcessSpawner.ExitCode(1))),
+                  kill: () => Deferred.succeed(killed, void 0).pipe(Effect.asVoid),
+                }),
+              ),
+            ),
+          );
+          const layerHttp = layerHttpClient((request) =>
+            Deferred.succeed(firstRequest, void 0).pipe(
+              Effect.as(
+                HttpClientResponse.fromWeb(request, new Response("Not a T3 endpoint", { status })),
+              ),
+            ),
+          );
+          const run = yield* DesktopBackendManager.runBackendProcess({
+            ...baseConfig,
+            desktopTelemetryStream: Stream.empty,
+            readinessTimeout: Duration.seconds(1),
+            onReady: () =>
+              Effect.sync(() => {
+                readyCount += 1;
+              }),
+          }).pipe(Effect.provide(Layer.merge(layerSpawner, layerHttp)), Effect.forkChild);
+
+          yield* Deferred.await(firstRequest);
+          yield* TestClock.adjust(Duration.seconds(2));
+
+          assert.isTrue(yield* Deferred.isDone(killed));
+          assert.equal(readyCount, 0);
+          assert.isTrue((yield* Fiber.join(run)).portTaken);
+        }).pipe(Effect.provide(TestClock.layer())),
+      ),
+  );
+
+  it.effect("reports a port collision when the child exits before the descriptor responds", () =>
+    Effect.scoped(
+      Effect.gen(function* () {
+        const requestStarted = yield* Deferred.make<void>();
+        const exitObserved = yield* Deferred.make<void>();
+        const respond = yield* Deferred.make<void>();
+        const layerSpawner = Layer.succeed(
+          ChildProcessSpawner.ChildProcessSpawner,
+          ChildProcessSpawner.make(() =>
+            Effect.succeed(
+              makeProcess({
+                exitCode: Deferred.await(requestStarted).pipe(
+                  Effect.as(ChildProcessSpawner.ExitCode(1)),
+                ),
+              }),
+            ),
+          ),
+        );
+        const layerHttp = layerHttpClient((request) =>
+          Deferred.succeed(requestStarted, void 0).pipe(
+            Effect.andThen(Deferred.await(respond)),
+            Effect.as(
+              HttpClientResponse.fromWeb(
+                request,
+                Response.json({ desktopLaunchId: "another-launch" }),
+              ),
+            ),
+          ),
+        );
+        const run = yield* DesktopBackendManager.runBackendProcess({
+          ...baseConfig,
+          desktopTelemetryStream: Stream.empty,
+          onExitObserved: () => Deferred.succeed(exitObserved, void 0).pipe(Effect.asVoid),
+        }).pipe(
+          Effect.provide(Layer.merge(layerSpawner, layerHttp)),
+          Effect.scoped,
+          Effect.forkChild,
+        );
+
+        yield* Deferred.await(exitObserved);
+        // Let exit handling settle while the descriptor request is still pending.
+        yield* TestClock.adjust(Duration.zero);
+        yield* Deferred.succeed(respond, void 0);
+
+        assert.isTrue((yield* Fiber.join(run)).portTaken);
+      }).pipe(Effect.provide(TestClock.layer())),
+    ),
+  );
+
+  it.effect("restarts on the port the spec moves to after another server took its port", () =>
+    Effect.scoped(
+      Effect.gen(function* () {
+        const port = yield* Ref.make(3773);
+        const spawnedPorts: Array<number> = [];
+        const takenPorts: Array<number> = [];
+        const ready = yield* Deferred.make<void>();
+        const layerSpawner = Layer.succeed(
+          ChildProcessSpawner.ChildProcessSpawner,
+          ChildProcessSpawner.make(() =>
+            Effect.gen(function* () {
+              spawnedPorts.push(yield* Ref.get(port));
+              const killed = yield* Deferred.make<void>();
+              return makeProcess({
+                exitCode: Deferred.await(killed).pipe(Effect.as(ChildProcessSpawner.ExitCode(1))),
+                kill: () => Deferred.succeed(killed, void 0).pipe(Effect.asVoid),
+              });
+            }),
+          ),
+        );
+        const layerHttp = layerHttpClient((request) =>
+          Effect.succeed(
+            HttpClientResponse.fromWeb(
+              request,
+              Response.json({
+                desktopLaunchId:
+                  new URL(request.url).port === "3773" ? "wsl-service" : "launch-3774",
+              }),
+            ),
+          ),
+        );
+
+        const instance = yield* makeTestInstance({
+          spawnerLayer: layerSpawner,
+          httpClientLayer: layerHttp,
+          configResolve: Ref.get(port).pipe(
+            Effect.map((current) => ({
+              ...baseConfig,
+              bootstrap: {
+                ...baseConfig.bootstrap,
+                port: current,
+                desktopLaunchId: `launch-${current}`,
+              },
+              httpBaseUrl: new URL(`http://127.0.0.1:${current}`),
+            })),
+          ),
+          onPortTaken: (taken) =>
+            Effect.sync(() => takenPorts.push(taken)).pipe(
+              Effect.andThen(Ref.set(port, taken + 1)),
+              Effect.as(true),
+            ),
+          onReady: Deferred.succeed(ready, void 0).pipe(Effect.asVoid),
+        });
+
+        yield* instance.start;
+        yield* Effect.yieldNow;
+        yield* TestClock.adjust(Duration.seconds(1));
+        yield* Deferred.await(ready);
+
+        assert.deepEqual(takenPorts, [3773]);
+        assert.deepEqual(spawnedPorts, [3773, 3774]);
+        const current = yield* instance.currentConfig;
+        assert.equal(Option.getOrThrow(current).httpBaseUrl.href, "http://127.0.0.1:3774/");
+        assert.isTrue((yield* instance.snapshot).ready);
+      }).pipe(Effect.provide(TestClock.layer())),
+    ),
+  );
+
   it.effect("starts the configured backend and closes the scoped process on stop", () =>
     Effect.scoped(
       Effect.gen(function* () {
```

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

Status: modified; +115; −41

```diff
@@ -24,7 +24,6 @@
   DesktopTelemetryControlMessage,
   type DesktopTelemetryControlMessage as DesktopTelemetryControlMessageValue,
 } from "@t3tools/contracts";
-import { waitForHttpReady as waitForHttpReadyShared } from "@t3tools/shared/httpReadiness";
 
 import * as DesktopObservability from "../app/DesktopObservability.ts";
 import * as DesktopTelemetryPublisher from "../telemetry/DesktopTelemetryPublisher.ts";
@@ -67,7 +66,7 @@
   // when false `env` is passed verbatim. WSL mode opts out so a leaking
   // T3CODE_HOME can't pin the WSL backend to /mnt/c/...\.t3.
   readonly extendEnv: boolean;
+  readonly bootstrap: DesktopBackendBootstrapValue & { readonly desktopLaunchId: string };
-  readonly bootstrap: DesktopBackendBootstrapValue;
   readonly bootstrapDelivery: DesktopBackendBootstrapDelivery;
   readonly httpBaseUrl: URL;
   readonly captureOutput: boolean;
@@ -85,6 +84,7 @@
 interface BackendProcessExit {
   readonly code: Option.Option<number>;
   readonly reason: string;
+  readonly portTaken: boolean;
 }
 
 const backendProcessContextSchema = {
@@ -243,6 +243,8 @@
   readonly onShutdown?: () => Effect.Effect<void>;
   readonly onPreflightFailed?: (failure: PreflightFailure) => Effect.Effect<boolean>;
   readonly onStateDirOwned?: () => Effect.Effect<void>;
+  // Return true after moving to another port to restart; false leaves the instance stopped.
+  readonly onPortTaken?: (port: number) => Effect.Effect<boolean>;
 }
 
 interface ActiveBackendRun {
@@ -312,27 +314,57 @@
   );
 };
 
+const BackendLaunchIdentity = Schema.fromJsonString(
+  Schema.Struct({ desktopLaunchId: Schema.String }),
+);
+const decodeBackendLaunchIdentity = Schema.decodeUnknownOption(BackendLaunchIdentity);
+
+type BackendOwnership = "launched" | "foreign";
+
+// Not an authenticated handshake; see docs/internals/environment-auth.md.
+export const checkBackendOwnership = Effect.fn("desktop.backend.checkOwnership")(function* (input: {
+  readonly httpBaseUrl: URL;
+  readonly desktopLaunchId: string;
+}) {
+  // A 5xx can be temporary during startup; permanent HTTP failures identify a foreign listener.
+  const client = (yield* HttpClient.HttpClient).pipe(
+    HttpClient.filterStatus((status) => status < 500),
+  );
+  const response = yield* client.get(new URL(BACKEND_READINESS_PATH, input.httpBaseUrl));
+  if (response.status < 200 || response.status >= 300) {
+    return "foreign" as const;
+  }
+  const body = yield* response.text;
+  return Option.match(decodeBackendLaunchIdentity(body), {
+    onNone: (): BackendOwnership => "foreign",
+    onSome: (identity): BackendOwnership =>
+      identity.desktopLaunchId === input.desktopLaunchId ? "launched" : "foreign",
+  });
+}, Effect.timeout(DEFAULT_BACKEND_READINESS_REQUEST_TIMEOUT));
+
 const waitForHttpReady = (
+  options: BackendProcessContext & {
-  options: BackendProcessContext & { readonly timeout: Duration.Duration },
-): Effect.Effect<void, BackendReadinessTimeoutError, HttpClient.HttpClient> => {
+    readonly timeout: Duration.Duration;
+    readonly desktopLaunchId: string;
+  },
+): Effect.Effect<BackendOwnership, BackendReadinessTimeoutError, HttpClient.HttpClient> => {
   const readinessUrl = new URL(BACKEND_READINESS_PATH, options.httpBaseUrl);
+  return checkBackendOwnership(options).pipe(
-  return waitForHttpReadyShared({
-    baseUrl: options.httpBaseUrl.href,
+    Effect.retry({ schedule: Schedule.spaced(DEFAULT_BACKEND_READINESS_INTERVAL) }),
+    Effect.timeout(options.timeout),
+    Effect.mapError(
+      (cause) =>
+        new BackendReadinessTimeoutError({
+          executablePath: options.executablePath,
+          entryPath: options.entryPath,
+          cwd: options.cwd,
+          httpBaseUrl: options.httpBaseUrl,
+          readinessUrl,
+          timeoutMs: Duration.toMillis(options.timeout),
+          cause,
+        }),
+    ),
+  );
-    path: BACKEND_READINESS_PATH,
-    timeoutMs: Duration.toMillis(options.timeout),
-    intervalMs: Duration.toMillis(DEFAULT_BACKEND_READINESS_INTERVAL),
-    probeTimeoutMs: Duration.toMillis(DEFAULT_BACKEND_READINESS_REQUEST_TIMEOUT),
-    makeError: ({ cause }) =>
-      new BackendReadinessTimeoutError({
-        executablePath: options.executablePath,
-        entryPath: options.entryPath,
-        cwd: options.cwd,
-        httpBaseUrl: options.httpBaseUrl,
-        readinessUrl,
-        timeoutMs: Duration.toMillis(options.timeout),
-        cause,
-      }),
-  });
 };
 
 function drainBackendOutput(
@@ -542,30 +574,44 @@
       ).pipe(Effect.forkScoped),
     );
   }
-  // Probe readiness in a loop while the backend process is still alive instead
-  // of giving up after the first budget. A slow cold boot (the WSL bundle
-  // loading across /mnt/c, or a first launch right after an update) can exceed
-  // the initial readiness budget while the backend is about to come up moments
-  // later; a one-shot probe left the app stuck on "Connecting to WSL…" forever
-  // even though the backend kept running and became healthy.
-  const probeReadiness = Effect.fn("desktop.backendProcess.probeReadiness")(() =>
-    waitForHttpReady({
-      executablePath: options.executablePath,
+  // Slow cold boots can outlast a readiness budget. Keep probing while the child lives.
+  const ownership = yield* Ref.make<BackendOwnership | undefined>(undefined);
-      entryPath: options.entryPath,
-      cwd: options.cwd,
-      httpBaseUrl: options.httpBaseUrl,
+  const exitObserved = yield* Ref.make(false);
+  const probeReadiness = Effect.fn("desktop.backendProcess.probeReadiness")(function* () {
+    const result = yield* waitForHttpReady({
+      ...options,
+      desktopLaunchId: options.bootstrap.desktopLaunchId,
       timeout: options.readinessTimeout ?? DEFAULT_BACKEND_READINESS_TIMEOUT,
     }).pipe(
-      Effect.flatMap(() => options.onReady?.() ?? Effect.void),
-      Effect.as(true),
       Effect.catchTags({
         BackendReadinessTimeoutError: (error) =>
+          (options.onReadinessFailure?.(error) ?? Effect.void).pipe(Effect.as(undefined)),
-          (options.onReadinessFailure?.(error) ?? Effect.void).pipe(Effect.as(false)),
       }),
+    );
+    if (result === undefined) {
+      return false;
+    }
+    yield* Ref.set(ownership, result);
+    if (yield* Ref.get(exitObserved)) {
+      return true;
+    }
+    if (result === "foreign") {
+      yield* logBackendProcessWarning("another server is answering on the backend port", {
+        httpBaseUrl: options.httpBaseUrl.href,
+      });
+      yield* handle
+        .kill({ killSignal: "SIGTERM", forceKillAfter: DEFAULT_BACKEND_TERMINATE_GRACE })
+        .pipe(Effect.ignore);
+      return true;
-    ),
-  );
+    }
+    yield* options.onReady?.() ?? Effect.void;
+    return true;
+  });
 
+  const readinessFiber = yield* probeReadiness().pipe(
-  yield* probeReadiness().pipe(Effect.repeat({ while: (ready) => !ready }), Effect.forkScoped);
+    Effect.repeat({ while: (done) => !done }),
+    Effect.forkScoped,
+  );
 
   const exit = yield* handle.exitCode.pipe(
     Effect.map((exitCode) => ({
@@ -596,7 +642,17 @@
     }),
     Effect.exit,
   );
+  yield* Ref.set(exitObserved, true);
   yield* options.onExitObserved?.() ?? Effect.void;
+  const stateDirOwned =
+    Exit.isSuccess(exit) &&
+    Option.exists(exit.value.code, (code) => code === SERVER_EXIT_CODE_STATE_DIR_OWNED);
+  if (!stateDirOwned && (yield* Ref.get(ownership)) === undefined) {
+    // A bind failure can exit before the pending descriptor identifies the foreign listener.
+    yield* Fiber.await(readinessFiber).pipe(
+      Effect.timeoutOption(DEFAULT_BACKEND_READINESS_REQUEST_TIMEOUT),
+    );
+  }
   yield* Effect.forEach(outputFibers, Fiber.await, {
     concurrency: "unbounded",
     discard: true,
@@ -604,10 +660,17 @@
     Effect.timeout(options.outputDrainTimeout ?? DEFAULT_BACKEND_OUTPUT_DRAIN_TIMEOUT),
     Effect.ignore,
   );
+  if (!stateDirOwned && (yield* Ref.get(ownership)) === "foreign") {
+    return {
+      code: Option.none(),
+      reason: `another server is answering on ${options.httpBaseUrl.href}`,
+      portTaken: true,
+    } satisfies BackendProcessExit;
+  }
   if (Exit.isFailure(exit)) {
     return yield* Effect.failCause(exit.cause);
   }
-  return exit.value;
+  return { ...exit.value, portTaken: false } satisfies BackendProcessExit;
 });
 
 export const makeBackendInstance = Effect.fn("makeBackendInstance")(function* (
@@ -797,6 +860,7 @@
         const finalizeRun = Effect.fn("desktop.backendInstance.finalizeRun")(function* (
           reason: string,
           exitCode?: number,
+          portTaken = false,
         ) {
           const stateDirOwned = exitCode === SERVER_EXIT_CODE_STATE_DIR_OWNED;
           yield* mutex.withPermits(1)(
@@ -873,6 +937,15 @@
                   yield* (spec.onStateDirOwned?.() ?? Effect.void).pipe(Effect.ignore);
                   return;
                 }
+                if (portTaken && !stopRequested) {
+                  const moved = yield* (
+                    spec.onPortTaken?.(config.value.bootstrap.port) ?? Effect.succeed(false)
+                  );
+                  if (!moved) {
+                    yield* Ref.update(state, (latest) => ({ ...latest, desiredRunning: false }));
+                    return;
+                  }
+                }
                 yield* scheduleRestart(reason);
               }
             }),
@@ -948,7 +1021,8 @@
           Scope.provide(runScope),
           Effect.matchEffect({
             onFailure: (error) => finalizeRun(error.message),
-            onSuccess: (exit) => finalizeRun(exit.reason, Option.getOrUndefined(exit.code)),
+            onSuccess: (exit) =>
+              finalizeRun(exit.reason, Option.getOrUndefined(exit.code), exit.portTaken),
           }),
           Effect.ensuring(Scope.close(runScope, Exit.void).pipe(Effect.ignore)),
         );
```

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

Status: modified; +16; −1

```diff
@@ -1,12 +1,14 @@
 import { assert, describe, it } from "@effect/vitest";
 import * as Effect from "effect/Effect";
 import * as FileSystem from "effect/FileSystem";
+import * as Config from "effect/Config";
 import * as Layer from "effect/Layer";
 import * as Option from "effect/Option";
 import * as Ref from "effect/Ref";
 import * as Stream from "effect/Stream";
 import { HttpClient } from "effect/http";
 import { ChildProcessSpawner } from "effect/process";
+import * as NetService from "@t3tools/shared/Net";
 
 import * as DesktopObservability from "../app/DesktopObservability.ts";
 import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
@@ -18,9 +20,12 @@
 import * as DesktopWslEnvironment from "../wsl/DesktopWslEnvironment.ts";
 import * as DesktopBackendConfiguration from "./DesktopBackendConfiguration.ts";
 import * as DesktopBackendPool from "./DesktopBackendPool.ts";
+import * as DesktopServerExposure from "./DesktopServerExposure.ts";
 import type { DesktopBackendStartConfig } from "./DesktopBackendManager.ts";
 
+function layerPool(
+  labelRef: Ref.Ref<string>,
-function layerPool(labelRef: Ref.Ref<string>): Layer.Layer<DesktopBackendPool.DesktopBackendPool> {
+): Layer.Layer<DesktopBackendPool.DesktopBackendPool, Config.ConfigError> {
   return DesktopBackendPool.layer.pipe(
     Layer.provideMerge(
       Layer.mergeAll(
@@ -63,6 +68,15 @@
         } satisfies DesktopBackendConfiguration.DesktopBackendConfiguration["Service"]),
         DesktopAppSettings.layerTest(),
         DesktopWslEnvironment.layerTest(),
+        NetService.layer,
+        Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
+          getState: Effect.die("unexpected getState"),
+          backendConfig: Effect.die("unexpected backendConfig"),
+          configureFromSettings: () => Effect.die("unexpected configureFromSettings"),
+          setMode: () => Effect.die("unexpected setMode"),
+          setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"),
+          getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"),
+        } satisfies DesktopServerExposure.DesktopServerExposure["Service"]),
         ElectronDialog.layer,
         ElectronApp.layer,
         Layer.succeed(DesktopWindow.DesktopWindow, {
@@ -111,6 +125,7 @@
               t3Home: "/tmp/t3",
               host: "127.0.0.1",
               desktopBootstrapToken: "token",
+              desktopLaunchId: "launch-1",
               tailscaleServeEnabled: false,
               tailscaleServePort: 443,
             },
```

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

Status: modified; +33; −0

```diff
@@ -11,9 +11,12 @@
 import * as FileSystem from "effect/FileSystem";
 import { HttpClient } from "effect/http";
 import { ChildProcessSpawner } from "effect/process";
+import * as NetService from "@t3tools/shared/Net";
 
 import * as DesktopBackendConfiguration from "./DesktopBackendConfiguration.ts";
 import * as DesktopBackendManager from "./DesktopBackendManager.ts";
+import * as DesktopServerExposure from "./DesktopServerExposure.ts";
+import * as DesktopConfig from "../app/DesktopConfig.ts";
 import * as DesktopObservability from "../app/DesktopObservability.ts";
 import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
 import * as DesktopTelemetryPublisher from "../telemetry/DesktopTelemetryPublisher.ts";
@@ -106,6 +109,9 @@
     const electronDialog = yield* ElectronDialog.ElectronDialog;
     const electronApp = yield* ElectronApp.ElectronApp;
     const appSettings = yield* DesktopAppSettings.DesktopAppSettings;
+    const { configuredBackendPort } = yield* DesktopConfig.DesktopConfig;
+    const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
+    const net = yield* NetService.NetService;
     // Without this, instance scopes are orphaned: they only close via explicit unregister() calls,
     // so on app shutdown the WSL backend child process gets hard-killed by the OS instead of
     // receiving the graceful SIGTERM + grace period the instance's stop finalizer would otherwise
@@ -156,6 +162,32 @@
       },
     );
 
+    // WSL localhost relays can appear after the startup scan. Only move unpinned ports.
+    const handlePrimaryPortTaken = Effect.fn("desktop.backendPool.primaryPortTaken")(function* (
+      port: number,
+    ) {
+      const nextPort = Option.isSome(configuredBackendPort)
+        ? Option.none<number>()
+        : yield* DesktopServerExposure.findAvailableBackendPort(port + 1).pipe(
+            Effect.provideService(NetService.NetService, net),
+            Effect.option,
+          );
+      if (Option.isNone(nextPort)) {
+        yield* electronDialog.showErrorBox(
+          `Port ${port} is in use`,
+          `Another server is answering on port ${port}, so T3 Code stopped its own backend there. Stop that server, or set T3CODE_PORT to a free port, and reopen T3 Code.`,
+        );
+        yield* electronApp.quit;
+        return false;
+      }
+      yield* logBackendPoolWarning("another server is answering on the backend port; moving", {
+        port,
+        nextPort: nextPort.value,
+      });
+      yield* serverExposure.configureFromSettings({ port: nextPort.value });
+      return true;
+    });
+
     const primary = yield* DesktopBackendManager.makeBackendInstance({
       id: DesktopBackendManager.PRIMARY_INSTANCE_ID,
       // Keep this lazy. The pool layer is initialized before startup loads
@@ -176,6 +208,7 @@
         ),
       onShutdown: () => desktopWindow.handleBackendNotReady,
       onPreflightFailed: handlePrimaryPreflightFailure,
+      onPortTaken: handlePrimaryPortTaken,
       onStateDirOwned: () =>
         electronDialog
           .showErrorBox(
```

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

Status: modified; +233; −39

```diff
@@ -1,8 +1,10 @@
 import { assert, describe, it } from "@effect/vitest";
+import * as Deferred from "effect/Deferred";
 import * as Duration from "effect/Duration";
 import * as Effect from "effect/Effect";
 import * as Fiber from "effect/Fiber";
 import * as Layer from "effect/Layer";
+import * as Option from "effect/Option";
 import * as Ref from "effect/Ref";
 import * as HttpClient from "effect/http/HttpClient";
 import * as HttpClientRequest from "effect/http/HttpClientRequest";
@@ -29,6 +31,7 @@
     t3Home: "/tmp/t3",
     host: "127.0.0.1",
     desktopBootstrapToken: "desktop-bootstrap-token",
+    desktopLaunchId: "launch-1",
     tailscaleServeEnabled: false,
     tailscaleServePort: 443,
   },
@@ -36,26 +39,43 @@
   captureOutput: true,
 };
 
+const DESCRIPTOR_URL = "http://127.0.0.1:3773/.well-known/t3/environment";
+
+const asLaunchedBackend =
+  (
+    handler: (
+      request: HttpClientRequest.HttpClientRequest,
+    ) => Effect.Effect<HttpClientResponse.HttpClientResponse>,
+  ) =>
+  (request: HttpClientRequest.HttpClientRequest) =>
+    request.url === DESCRIPTOR_URL
+      ? Effect.succeed(
+          HttpClientResponse.fromWeb(request, Response.json({ desktopLaunchId: "launch-1" })),
+        )
+      : handler(request);
+
 describe("DesktopLocalEnvironmentAuth", () => {
   it.effect("exchanges the desktop bootstrap credential only once", () =>
     Effect.gen(function* () {
       const requestCount = yield* Ref.make(0);
       const layerHttpClient = Layer.succeed(
         HttpClient.HttpClient,
+        HttpClient.make(
+          asLaunchedBackend((request) =>
-        HttpClient.make((request) =>
-          Ref.update(requestCount, (count) => count + 1).pipe(
-            Effect.as(
-              HttpClientResponse.fromWeb(
-                request,
-                new Response(
-                  JSON.stringify({
-                    access_token: "desktop-bearer-token",
-                    issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
-                    token_type: "Bearer",
-                    expires_in: 3600,
-                    scope: "orchestration:read",
-                  }),
-                  { status: 200, headers: { "content-type": "application/json" } },
+            Ref.update(requestCount, (count) => count + 1).pipe(
+              Effect.as(
+                HttpClientResponse.fromWeb(
+                  request,
+                  new Response(
+                    JSON.stringify({
+                      access_token: "desktop-bearer-token",
+                      issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
+                      token_type: "Bearer",
+                      expires_in: 3600,
+                      scope: "orchestration:read",
+                    }),
+                    { status: 200, headers: { "content-type": "application/json" } },
+                  ),
                 ),
               ),
             ),
@@ -93,27 +113,31 @@
         const presented = yield* Ref.make<string | null>(null);
         const httpClientLayer = Layer.succeed(
           HttpClient.HttpClient,
-          HttpClient.make((request) => {
-            const body =
-              request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : "";
+          HttpClient.make(
+            asLaunchedBackend((request) => {
+              const body =
-            return Ref.set(presented, new URLSearchParams(body).get("subject_token")).pipe(
+                request.body._tag === "Uint8Array"
-              Effect.as(
-                HttpClientResponse.fromWeb(
-                  request,
-                  new Response(
-                    JSON.stringify({
-                      access_token: "desktop-bearer-token",
-                      issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
-                      token_type: "Bearer",
-                      expires_in: 3600,
-                      scope: "orchestration:read",
-                    }),
-                    { status: 200, headers: { "content-type": "application/json" } },
+                  ? new TextDecoder().decode(request.body.body)
+                  : "";
+              return Ref.set(presented, new URLSearchParams(body).get("subject_token")).pipe(
+                Effect.as(
+                  HttpClientResponse.fromWeb(
+                    request,
+                    new Response(
+                      JSON.stringify({
+                        access_token: "desktop-bearer-token",
+                        issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
+                        token_type: "Bearer",
+                        expires_in: 3600,
+                        scope: "orchestration:read",
+                      }),
+                      { status: 200, headers: { "content-type": "application/json" } },
+                    ),
                   ),
                 ),
-              ),
+              );
+            }),
+          ),
-            );
-          }),
         );
         const poolLayer = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
           list: Effect.succeed([
@@ -175,12 +199,14 @@
       const requestCount = yield* Ref.make(0);
       const layerHttpClient = Layer.succeed(
         HttpClient.HttpClient,
+        HttpClient.make(
+          asLaunchedBackend((request) =>
+            Ref.updateAndGet(requestCount, (count) => count + 1).pipe(
+              Effect.map((count) =>
-        HttpClient.make((request) =>
-          Ref.updateAndGet(requestCount, (count) => count + 1).pipe(
-            Effect.map((count) =>
-              count <= failures
-                ? HttpClientResponse.fromWeb(request, failure())
-                : tokenResponse(request),
+                count <= failures
+                  ? HttpClientResponse.fromWeb(request, failure())
+                  : tokenResponse(request),
+              ),
             ),
           ),
         ),
@@ -217,4 +243,172 @@
       assert.strictEqual(yield* Ref.get(requestCount), 1);
     }).pipe(Effect.provide(TestClock.layer())),
   );
+
+  it.effect.each([
+    { name: "a page that is not a T3 server", body: () => new Response("<html></html>") },
+    { name: "a T3 server launched without an id", body: () => Response.json({ label: "WSL" }) },
+  ])("never sends the bootstrap token to $name", ({ body }) =>
+    Effect.gen(function* () {
+      const requests = yield* Ref.make<ReadonlyArray<string>>([]);
+      const layerHttpClient = Layer.succeed(
+        HttpClient.HttpClient,
+        HttpClient.make((request) =>
+          Ref.update(requests, (seen) => [...seen, `${request.method} ${request.url}`]).pipe(
+            Effect.as(
+              request.url === DESCRIPTOR_URL
+                ? HttpClientResponse.fromWeb(request, body())
+                : tokenResponse(request),
+            ),
+          ),
+        ),
+      );
+
+      const error = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth.pipe(
+        Effect.flatMap((auth) => auth.getBearerToken),
+        Effect.flip,
+        Effect.provide(
+          DesktopLocalEnvironmentAuth.layer.pipe(
+            Layer.provide(Layer.mergeAll(layerPool, layerHttpClient)),
+          ),
+        ),
+      );
+
+      assert.strictEqual(error._tag, "DesktopLocalEnvironmentAuthForeignServerError");
+      assert.include(error.message, "port 3773");
+      assert.deepEqual(yield* Ref.get(requests), [`GET ${DESCRIPTOR_URL}`]);
+    }),
+  );
+
+  it.effect("uses the current backend config when retrying after a port move", () =>
+    Effect.gen(function* () {
+      const firstExchange = yield* Deferred.make<void>();
+      let currentConfig = config;
+      const requests: string[] = [];
+      const credentials: string[] = [];
+      const layerHttpClient = Layer.succeed(
+        HttpClient.HttpClient,
+        HttpClient.make((request) => {
+          requests.push(`${request.method} ${request.url}`);
+          if (request.method === "GET") {
+            return Effect.succeed(
+              HttpClientResponse.fromWeb(
+                request,
+                Response.json({
+                  desktopLaunchId:
+                    request.url === DESCRIPTOR_URL && currentConfig !== config
+                      ? "foreign-launch"
+                      : currentConfig.bootstrap.desktopLaunchId,
+                }),
+              ),
+            );
+          }
+          const body =
+            request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : "";
+          credentials.push(new URLSearchParams(body).get("subject_token") ?? "");
+          return request.url === "http://127.0.0.1:3773/oauth/token"
+            ? Deferred.succeed(firstExchange, void 0).pipe(
+                Effect.as(HttpClientResponse.fromWeb(request, new Response("", { status: 503 }))),
+              )
+            : Effect.succeed(tokenResponse(request));
+        }),
+      );
+      const movingPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
+        list: Effect.succeed([
+          {
+            id: PRIMARY_LOCAL_ENVIRONMENT_ID,
+            label: Effect.succeed("Windows"),
+            currentConfig: Effect.sync(() => Option.some(currentConfig)),
+          },
+        ]),
+      } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]);
+      const result = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth.pipe(
+        Effect.flatMap((auth) => auth.getBearerToken),
+        Effect.result,
+        Effect.provide(
+          DesktopLocalEnvironmentAuth.layer.pipe(
+            Layer.provide(Layer.mergeAll(movingPool, layerHttpClient)),
+          ),
+        ),
+        Effect.forkChild,
+      );
+
+      yield* Deferred.await(firstExchange);
+      currentConfig = {
+        ...config,
+        httpBaseUrl: new URL("http://127.0.0.1:3774"),
+        bootstrap: {
+          ...config.bootstrap,
+          port: 3774,
+          desktopLaunchId: "launch-2",
+          desktopBootstrapToken: "restarted-bootstrap-token",
+        },
+      };
+      yield* TestClock.adjust(Duration.seconds(15));
+
+      const outcome = yield* Fiber.join(result);
+      assert.equal(outcome._tag, "Success");
+      if (outcome._tag === "Success") {
+        assert.strictEqual(outcome.success, "desktop-bearer-token");
+      }
+      assert.deepEqual(requests, [
+        `GET ${DESCRIPTOR_URL}`,
+        "POST http://127.0.0.1:3773/oauth/token",
+        "GET http://127.0.0.1:3774/.well-known/t3/environment",
+        "POST http://127.0.0.1:3774/oauth/token",
+      ]);
+      assert.deepEqual(credentials, ["desktop-bootstrap-token", "restarted-bootstrap-token"]);
+    }).pipe(Effect.provide(TestClock.layer())),
+  );
+
+  it.effect("rechecks ownership before retrying the bootstrap token exchange", () =>
+    Effect.gen(function* () {
+      const firstExchange = yield* Deferred.make<void>();
+      const requests: string[] = [];
+      let exchanged = false;
+      const layerHttpClient = Layer.succeed(
+        HttpClient.HttpClient,
+        HttpClient.make((request) => {
+          requests.push(`${request.method} ${new URL(request.url).pathname}`);
+          if (request.url === DESCRIPTOR_URL) {
+            return Effect.succeed(
+              HttpClientResponse.fromWeb(
+                request,
+                Response.json({
+                  desktopLaunchId: exchanged ? "another-launch" : config.bootstrap.desktopLaunchId,
+                }),
+              ),
+            );
+          }
+          exchanged = true;
+          return Deferred.succeed(firstExchange, void 0).pipe(
+            Effect.as(HttpClientResponse.fromWeb(request, new Response("", { status: 503 }))),
+          );
+        }),
+      );
+      const result = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth.pipe(
+        Effect.flatMap((auth) => auth.getBearerToken),
+        Effect.result,
+        Effect.provide(
+          DesktopLocalEnvironmentAuth.layer.pipe(
+            Layer.provide(Layer.mergeAll(layerPool, layerHttpClient)),
+          ),
+        ),
+        Effect.forkChild,
+      );
+
+      yield* Deferred.await(firstExchange);
+      yield* TestClock.adjust(Duration.seconds(15));
+
+      const outcome = yield* Fiber.join(result);
+      assert.equal(outcome._tag, "Failure");
+      if (outcome._tag === "Failure") {
+        assert.equal(outcome.failure._tag, "DesktopLocalEnvironmentAuthForeignServerError");
+      }
+      assert.deepEqual(requests, [
+        "GET /.well-known/t3/environment",
+        "POST /oauth/token",
+        "GET /.well-known/t3/environment",
+      ]);
+    }).pipe(Effect.provide(TestClock.layer())),
+  );
 });
```

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

Status: modified; +61; −31

```diff
@@ -14,6 +14,7 @@
 import * as Semaphore from "effect/Semaphore";
 import * as HttpClient from "effect/http/HttpClient";
 
+import * as DesktopBackendManager from "./DesktopBackendManager.ts";
 import * as DesktopBackendPool from "./DesktopBackendPool.ts";
 
 const BOOTSTRAP_TRANSIENT_RETRY_TIMEOUT = Duration.seconds(15);
@@ -50,9 +51,19 @@
   }
 }
 
+export class DesktopLocalEnvironmentAuthForeignServerError extends Schema.TaggedError<DesktopLocalEnvironmentAuthForeignServerError>()(
+  "DesktopLocalEnvironmentAuthForeignServerError",
+  { port: Schema.Number },
+) {
+  override get message(): string {
+    return `Another server is answering on port ${this.port} in place of the T3 Code backend this app started, so the desktop did not sign in to it. Stop that server and reopen T3 Code.`;
+  }
+}
+
 export const DesktopLocalEnvironmentAuthError = Schema.Union([
   DesktopLocalEnvironmentAuthBackendNotConfiguredError,
   DesktopLocalEnvironmentAuthSessionBootstrapError,
+  DesktopLocalEnvironmentAuthForeignServerError,
 ]);
 export type DesktopLocalEnvironmentAuthError = typeof DesktopLocalEnvironmentAuthError.Type;
 
@@ -78,43 +89,62 @@
           return cached.value;
         }
 
-        const instances = yield* pool.list;
-        const primary = instances.find((instance) => instance.id === PRIMARY_LOCAL_ENVIRONMENT_ID);
-        const configOption = primary === undefined ? Option.none() : yield* primary.currentConfig;
-        if (Option.isNone(configOption)) {
-          return yield* new DesktopLocalEnvironmentAuthBackendNotConfiguredError();
-        }
-        const config = configOption.value;
-        // A backend launched with the desktop secret accepts the current
-        // window's token, not the one frozen into its launch config; this
-        // exchange can run long after launch (e.g. after a suspend).
-        const secret = config.bootstrap.desktopBootstrapSecret;
-        const credential =
-          secret === undefined
+        // Recheck before each POST, including retries; the port may have changed since readiness.
-            ? config.bootstrap.desktopBootstrapToken
+        const session = yield* Effect.gen(function* () {
+          const instances = yield* pool.list;
+          const primary = instances.find(
+            (instance) => instance.id === PRIMARY_LOCAL_ENVIRONMENT_ID,
+          );
+          const configOption = primary === undefined ? Option.none() : yield* primary.currentConfig;
+          if (Option.isNone(configOption)) {
+            return yield* new DesktopLocalEnvironmentAuthBackendNotConfiguredError();
+          }
-            : currentDesktopBootstrapToken(secret, yield* Clock.currentTimeMillis);
-        if (!credential) {
-          return yield* new DesktopLocalEnvironmentAuthBackendNotConfiguredError();
-        }
-        const session = yield* bootstrapRemoteBearerSession({
-          httpBaseUrl: config.httpBaseUrl.href,
-          credential,
-          clientMetadata: {
-            label: "T3 Code Desktop",
-            deviceType: "desktop",
-          },
+          const config = configOption.value;
+          // A backend launched with the desktop secret accepts the current
+          // window's token, not the one frozen into its launch config; this
+          // exchange can run long after launch (e.g. after a suspend).
+          const secret = config.bootstrap.desktopBootstrapSecret;
+          const credential =
+            secret === undefined
+              ? config.bootstrap.desktopBootstrapToken
+              : currentDesktopBootstrapToken(secret, yield* Clock.currentTimeMillis);
+          if (!credential) {
+            return yield* new DesktopLocalEnvironmentAuthBackendNotConfiguredError();
+          }
+          const ownership = yield* DesktopBackendManager.checkBackendOwnership({
+            httpBaseUrl: config.httpBaseUrl,
+            desktopLaunchId: config.bootstrap.desktopLaunchId,
+          });
+          if (ownership === "foreign") {
+            return yield* new DesktopLocalEnvironmentAuthForeignServerError({
+              port: config.bootstrap.port,
+            });
+          }
+          return yield* bootstrapRemoteBearerSession({
+            httpBaseUrl: config.httpBaseUrl.href,
+            credential,
+            clientMetadata: {
+              label: "T3 Code Desktop",
+              deviceType: "desktop",
+            },
+          });
         }).pipe(
           Effect.provideService(HttpClient.HttpClient, httpClient),
           Effect.retry({
+            while: (error) =>
+              error._tag === "HttpClientError" ||
+              error._tag === "TimeoutError" ||
+              (error._tag !== "DesktopLocalEnvironmentAuthForeignServerError" &&
+                error._tag !== "DesktopLocalEnvironmentAuthBackendNotConfiguredError" &&
+                isTransientBearerBootstrapError(error)),
-            while: isTransientBearerBootstrapError,
             schedule: Schedule.spaced(BOOTSTRAP_TRANSIENT_RETRY_INTERVAL),
           }),
           Effect.timeout(BOOTSTRAP_TRANSIENT_RETRY_TIMEOUT),
+          Effect.mapError((cause) =>
+            cause._tag === "DesktopLocalEnvironmentAuthForeignServerError" ||
+            cause._tag === "DesktopLocalEnvironmentAuthBackendNotConfiguredError"
+              ? cause
+              : new DesktopLocalEnvironmentAuthSessionBootstrapError({ cause }),
-          Effect.mapError(
-            (cause) =>
-              new DesktopLocalEnvironmentAuthSessionBootstrapError({
-                cause,
-              }),
           ),
         );
         yield* Ref.set(tokenRef, Option.some(session.access_token));
```

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

Status: modified; +42; −0

```diff
@@ -21,6 +21,7 @@
 import * as Semaphore from "effect/Semaphore";
 import * as HttpClient from "effect/http/HttpClient";
 import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
+import * as NetService from "@t3tools/shared/Net";
 
 import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
 import * as DesktopConfig from "../app/DesktopConfig.ts";
@@ -31,7 +32,48 @@
 
 const DESKTOP_LOOPBACK_HOST = "127.0.0.1";
 const DESKTOP_LAN_BIND_HOST = "0.0.0.0";
+const MAX_TCP_PORT = 65_535;
+const DESKTOP_BACKEND_PORT_PROBE_HOSTS = ["127.0.0.1", "0.0.0.0", "::"] as const;
 
+export class DesktopBackendPortUnavailableError extends Schema.TaggedError<DesktopBackendPortUnavailableError>()(
+  "DesktopBackendPortUnavailableError",
+  {
+    startPort: Schema.Int,
+    maxPort: Schema.Int,
+    hosts: Schema.Array(Schema.String),
+  },
+) {
+  override get message(): string {
+    return `No desktop backend port is available on hosts ${this.hosts.join(", ")} between ${this.startPort} and ${this.maxPort}.`;
+  }
+}
+
+export const findAvailableBackendPort = Effect.fn(
+  "desktop.serverExposure.findAvailableBackendPort",
+)(function* (startPort: number) {
+  const net = yield* NetService.NetService;
+  for (let port = startPort; port <= MAX_TCP_PORT; port += 1) {
+    let availableOnEveryHost = true;
+
+    for (const host of DESKTOP_BACKEND_PORT_PROBE_HOSTS) {
+      if (!(yield* net.canListenOnHost(port, host))) {
+        availableOnEveryHost = false;
+        break;
+      }
+    }
+
+    if (availableOnEveryHost) {
+      return port;
+    }
+  }
+
+  return yield* new DesktopBackendPortUnavailableError({
+    startPort,
+    maxPort: MAX_TCP_PORT,
+    hosts: DESKTOP_BACKEND_PORT_PROBE_HOSTS,
+  });
+});
+
 interface ResolvedDesktopServerExposure {
   readonly mode: DesktopServerExposureMode;
   readonly bindHost: string;
```

## apps/desktop/src/ipc/methods/window.test.ts

Status: modified; +2; −0

```diff
@@ -39,6 +39,7 @@
     port: 3774,
     host: "0.0.0.0",
     desktopBootstrapToken: "bootstrap-token",
+    desktopLaunchId: "launch-1",
     tailscaleServeEnabled: false,
     tailscaleServePort: 443,
   },
@@ -90,6 +91,7 @@
           runningDistro: "Ubuntu",
           httpBaseUrl: "http://127.0.0.1:3774/",
           wsBaseUrl: "ws://127.0.0.1:3774/",
+          desktopLaunchId: "launch-1",
           bootstrapToken: "current-window-token",
         },
       ]);
```

## apps/desktop/src/ipc/methods/window.ts

Status: modified; +1; −0

```diff
@@ -138,6 +138,7 @@
         runningDistro,
         httpBaseUrl: httpBaseUrl.href,
         wsBaseUrl: toWebSocketBaseUrl(httpBaseUrl),
+        desktopLaunchId: bootstrap.desktopLaunchId,
         // A backend launched with the desktop secret accepts whichever token the secret derives
         // for the current window, so hand out that one rather than the token frozen into its
         // launch config.
```

## apps/desktop/src/wsl/DesktopWslBackend.ts

Status: modified; +26; −3

```diff
@@ -114,12 +114,12 @@
       if (Option.isNone(port)) {
         return;
       }
-      const allocatedPort = port.value;
+      const allocatedPort = yield* Ref.make(port.value);
 
       const targetId = resolveTargetInstanceId(input.distro);
       yield* logWslBackendInfo("registering WSL backend with pool", {
         id: targetId,
-        port: allocatedPort,
+        port: port.value,
         distro: input.distro ?? null,
       });
 
@@ -127,7 +127,30 @@
         .register({
           id: targetId,
           label: Effect.succeed(buildLabel(input.distro)),
+          configResolve: Ref.get(allocatedPort).pipe(
+            Effect.flatMap((current) =>
+              configuration.resolveWsl({ port: current, distro: input.distro }),
+            ),
+          ),
+          onPortTaken: (takenPort) =>
+            scanForWslPort(takenPort + 1).pipe(
+              Effect.provideService(NetService.NetService, net),
+              Effect.tap((nextPort) =>
+                logWslBackendWarning(
+                  "another server is answering on the WSL backend port; moving",
+                  {
+                    id: targetId,
+                    port: takenPort,
+                    nextPort,
+                  },
+                ),
-          configResolve: configuration.resolveWsl({ port: allocatedPort, distro: input.distro }),
+              ),
+              Effect.flatMap((nextPort) => Ref.set(allocatedPort, nextPort)),
+              Effect.as(true),
+              Effect.catch((error) =>
+                Ref.set(preflightErrorRef, Option.some(error.message)).pipe(Effect.as(false)),
+              ),
+            ),
           onPreflightFailed: (failure) =>
             Ref.set(preflightErrorRef, Option.some(failure.reason)).pipe(Effect.as(false)),
           onStateDirOwned: () =>
```

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

Status: modified; +2; −0

```diff
@@ -364,6 +364,7 @@
   );
   const desktopBootstrapToken = bootstrap?.desktopBootstrapToken;
   const desktopBootstrapSecret = bootstrap?.desktopBootstrapSecret;
+  const desktopLaunchId = bootstrap?.desktopLaunchId;
   const shellEnvironmentPrepared = bootstrap?.shellEnvironmentPrepared;
   const desktopTelemetryFd = bootstrap?.desktopTelemetryFd;
   const desktopTelemetryControlFd = bootstrap?.desktopTelemetryControlFd;
@@ -471,6 +472,7 @@
     startupPresentation,
     desktopBootstrapToken,
     ...(desktopBootstrapSecret === undefined ? {} : { desktopBootstrapSecret }),
+    ...(desktopLaunchId === undefined ? {} : { desktopLaunchId }),
     ...(shellEnvironmentPrepared === undefined ? {} : { shellEnvironmentPrepared }),
     desktopTelemetryFd,
     desktopTelemetryControlFd,
```

## apps/server/src/config.ts

Status: modified; +1; −0

```diff
@@ -75,6 +75,7 @@
     readonly startupPresentation: StartupPresentation;
     readonly desktopBootstrapToken: string | undefined;
     readonly desktopBootstrapSecret?: string | undefined;
+    readonly desktopLaunchId?: string | undefined;
     readonly shellEnvironmentPrepared?: boolean | undefined;
     readonly desktopTelemetryFd?: number | undefined;
     readonly desktopTelemetryControlFd?: number | undefined;
```

## apps/server/src/environment/ServerEnvironment.ts

Status: modified; +3; −0

```diff
@@ -215,6 +215,9 @@
     },
     serverVersion: packageJson.version,
     orchestrationProtocolVersion: ORCHESTRATION_PROTOCOL_VERSION,
+    ...(serverConfig.desktopLaunchId === undefined
+      ? {}
+      : { desktopLaunchId: serverConfig.desktopLaunchId }),
     capabilities: {
       repositoryIdentity: true,
       connectionProbe: true,
```

## apps/web/src/connection/desktopLocal.ts

Status: modified; +2; −1

```diff
@@ -80,7 +80,8 @@
             entry.runningDistro !== previous.runningDistro ||
             entry.httpBaseUrl !== previous.httpBaseUrl ||
             entry.wsBaseUrl !== previous.wsBaseUrl ||
+            entry.bootstrapToken !== previous.bootstrapToken ||
+            entry.desktopLaunchId !== previous.desktopLaunchId
-            entry.bootstrapToken !== previous.bootstrapToken
           );
         })
       ) {
```

## apps/web/src/connection/platform.test.ts

Status: modified; +75; −1

```diff
@@ -7,9 +7,11 @@
   type DesktopSshEnvironmentTarget,
 } from "@t3tools/contracts";
 import { describe, expect, it } from "@effect/vitest";
+import { afterEach, vi } from "vite-plus/test";
 import * as Effect from "effect/Effect";
+import * as Layer from "effect/Layer";
+import { HttpClient, HttpClientResponse } from "effect/http";
 import * as Fiber from "effect/Fiber";
-import { afterEach, vi } from "vite-plus/test";
 import { ClientCapabilities } from "@t3tools/client-runtime/platform";
 import {
   ConnectionBlockedError,
@@ -22,6 +24,7 @@
   layer,
   isRejectedBootstrapCredentialError,
   isRejectedSecondaryBootstrap,
+  loadSecondaryConnectionRegistration,
   nextRejectedSecondaryBootstrap,
   primaryRegistrationToRetainAfterTopologyRead,
   prepareDesktopSshEnvironment,
@@ -412,3 +415,74 @@
     ).toBe(cached);
   });
 });
+
+describe("desktop-local secondary bootstrap", () => {
+  afterEach(() => vi.unstubAllGlobals());
+  it.effect("waits for the launched backend before exchanging the secondary bootstrap token", () =>
+    Effect.gen(function* () {
+      const requests: string[] = [];
+      let desktopLaunchId = "launch-from-another-app";
+      vi.stubGlobal("window", { desktopBridge: { getClientPlatform: () => "win32" } });
+      const layerHttpClient = Layer.succeed(
+        HttpClient.HttpClient,
+        HttpClient.make((request) => {
+          requests.push(`${request.method} ${new URL(request.url).pathname}`);
+          if (request.method === "POST") {
+            return Effect.succeed(
+              HttpClientResponse.fromWeb(
+                request,
+                Response.json({
+                  access_token: "secondary-bearer",
+                  issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
+                  token_type: "Bearer",
+                  expires_in: 3600,
+                  scope: "orchestration:read",
+                }),
+              ),
+            );
+          }
+          return Effect.succeed(
+            HttpClientResponse.fromWeb(
+              request,
+              Response.json({
+                environmentId: "environment-wsl-service",
+                label: "WSL service",
+                platform: { os: "linux", arch: "x64" },
+                serverVersion: "0.0.44",
+                desktopLaunchId,
+                capabilities: { repositoryIdentity: true },
+              }),
+            ),
+          );
+        }),
+      );
+
+      const bootstrap = {
+        id: "wsl:default",
+        label: "WSL",
+        httpBaseUrl: "http://127.0.0.1:3774/",
+        wsBaseUrl: "ws://127.0.0.1:3774/",
+        bootstrapToken: "bootstrap-token",
+        desktopLaunchId: "launch-1",
+      };
+      const error = yield* loadSecondaryConnectionRegistration(bootstrap).pipe(
+        Effect.flip,
+        Effect.provide(layerHttpClient),
+      );
+
+      expect(error).toBeInstanceOf(ConnectionTransientError);
+      expect(requests).toEqual(["GET /.well-known/t3/environment"]);
+
+      desktopLaunchId = bootstrap.desktopLaunchId;
+      const connected = yield* loadSecondaryConnectionRegistration(bootstrap).pipe(
+        Effect.provide(layerHttpClient),
+      );
+      expect(connected.registration.credential.token).toBe("secondary-bearer");
+      expect(requests).toEqual([
+        "GET /.well-known/t3/environment",
+        "GET /.well-known/t3/environment",
+        "POST /oauth/token",
+      ]);
+    }),
+  );
+});
```

## apps/web/src/connection/platform.ts

Status: modified; +7; −1

```diff
@@ -433,7 +433,7 @@
   });
 });
 
-const loadSecondaryConnectionRegistration = Effect.fn(
+export const loadSecondaryConnectionRegistration = Effect.fn(
   "web.connectionPlatform.loadSecondaryConnectionRegistration",
 )(function* (entry: DesktopEnvironmentBootstrap) {
   if (
@@ -451,6 +451,12 @@
   const descriptor = yield* fetchRemoteEnvironmentDescriptor({ httpBaseUrl }).pipe(
     Effect.mapError(mapRemoteEnvironmentError),
   );
+  if (entry.desktopLaunchId !== undefined && descriptor.desktopLaunchId !== entry.desktopLaunchId) {
+    return yield* new ConnectionTransientError({
+      reason: "endpoint-unavailable",
+      detail: `Another server is answering at ${httpBaseUrl} in place of desktop-local backend ${entry.id}.`,
+    });
+  }
   const issuedAtEpochMs = yield* Clock.currentTimeMillis;
   const access = yield* bootstrapRemoteBearerSession({
     httpBaseUrl,
```

## docs/internals/environment-auth.md

Status: modified; +8; −0

```diff
@@ -85,6 +85,14 @@
 replacement must leave the old credential usable. Pairing and browser sessions
 do not follow this replacement rule.
 
+Desktop launches carry a fresh public `desktopLaunchId`, echoed in the environment
+descriptor and checked before readiness, fresh bearer exchanges in desktop main,
+and renderer secondary bootstrap exchanges (including retries). This detects
+accidental port collisions, including delayed WSL localhost relays; it does not
+authenticate ownership. A process that copies the id can pass, and the descriptor
+GET/token POST race remains. Cached bearers continue to be reused without a new
+check.
+
 ### Reusable dev credential
 
 Web development environments can accept one `T3CODE_DEV_AUTH_TOKEN` across
```

## packages/contracts/src/desktopBootstrap.ts

Status: modified; +2; −0

```diff
@@ -20,6 +20,8 @@
   // `desktopBootstrapToken` for its whole run. See
   // `@t3tools/shared/desktopBootstrapToken`.
   desktopBootstrapSecret: Schema.optionalKey(Schema.String),
+  // Public correlation id echoed by the descriptor; fresh for each backend launch.
+  desktopLaunchId: Schema.optionalKey(TrimmedNonEmptyString),
   // Set only when the desktop already merged the user's shell environment into
   // the environment it hands this backend. Absent means the backend hydrates
   // PATH itself, which WSL and older desktops rely on.
```

[Next page](https://git.cafe/pingdotgg/t3code/pull/21102/diffs?after=InBhY2thZ2VzL2NvbnRyYWN0cy9zcmMvZGVza3RvcEJvb3RzdHJhcC50cyI&expectedVersion=8&format=markdown)
