From 544884e9bb888d818cd1398395c1c8ce6c0b16d5 Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Fri, 9 Oct 2026 22:58:50 +0530 Subject: [PATCH 1/5] fix(ssh): pair reused servers with their own CLI version --- packages/ssh/src/tunnel.test.ts | 178 +++++++++++++++++++++++++++++++- packages/ssh/src/tunnel.ts | 45 +++++++- 2 files changed, 218 insertions(+), 5 deletions(-) diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index d0943d1e382f..101f510ada14 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -16,7 +16,8 @@ import { HttpClient, HttpClientResponse } from "effect/http"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import * as SshAuth from "./auth.ts"; -import { SshCommandError } from "./errors.ts"; +import { remoteStateKey } from "./command.ts"; +import { SshCommandError, SshPairingError } from "./errors.ts"; import * as SshTunnel from "./tunnel.ts"; const TEST_NODE_ENGINE_RANGE = "^22.16 || ^23.11 || >=24.10"; @@ -484,6 +485,181 @@ describe("ssh tunnel scripts", () => { }).pipe(Effect.provide(layerProcess)); }); + describe.skipIf(HostProcessPlatform.defaultValue() === "win32")("SSH release pairing", () => { + it.effect.each(["0.0.46-nightly.20261004.2652", ARCHIVE.archiveVersion, "1.2.4", "0.0.0-dev"])( + "mints pairing tokens with the running server's CLI version %s", + (serverVersion) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const localSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-ssh-pairing-" }); + for (const version of new Set([serverVersion, ARCHIVE.archiveVersion])) { + const runtime = `${home}/.t3/runtime/versions/${version}`; + yield* fs.makeDirectory(runtime, { recursive: true }); + yield* fs.writeFileString(`${runtime}/.install-complete`, version); + yield* fs.writeFileString( + `${runtime}/t3`, + `#!/bin/sh\nprintf '%s\\n' '{"credential":"paired-${version}"}'\n`, + ); + yield* fs.chmod(`${runtime}/t3`, 0o700); + } + const runner = + serverVersion === "0.0.0-dev" ? { nodeScriptPath: `${home}/dev.mjs` } : ARCHIVE; + if ("nodeScriptPath" in runner) { + yield* fs.writeFileString( + runner.nodeScriptPath, + `process.stdout.write(JSON.stringify({ credential: "paired-${serverVersion}" }));`, + ); + } + const target = { alias: "devbox", hostname: "devbox", username: null, port: null }; + const stateKey = yield* remoteStateKey(target); + const launchRunner = `${home}/.t3/ssh-launch/${stateKey}/run-t3.sh`; + const launchScript = SshTunnel.buildRemoteT3RunnerScript(runner); + yield* fs.makeDirectory(`${home}/.t3/ssh-launch/${stateKey}`, { recursive: true }); + yield* fs.writeFileString(launchRunner, launchScript); + let launches = 0; + let descriptorRequests = 0; + const spawner = ChildProcessSpawner.make((command) => + Effect.gen(function* () { + const args = commandArgs(command); + if (args.includes("-N")) return makeRunningProcess(() => undefined); + if (args.includes("--")) { + launches += 1; + return makeSuccessfulProcess('{"remotePort":3773,"serverKind":"external"}\n'); + } + if (command._tag === "StandardCommand" && args.includes("sh")) { + const stdin = command.options.stdin; + if ( + typeof stdin === "object" && + "stream" in stdin && + Stream.isStream(stdin.stream) + ) { + const script = yield* stdin.stream.pipe( + Stream.decodeText(), + Stream.runFold( + () => "", + (text, chunk) => text + chunk, + ), + ); + if (script.includes("PAIRING_BASE_DIR=")) { + return yield* localSpawner.spawn( + ChildProcess.make("sh", ["-s"], { + env: { HOME: home, PATH: process.env.PATH ?? "" }, + stdin: { + stream: Stream.make(new TextEncoder().encode(script)), + endOnDone: true, + }, + }), + ); + } + } + } + return makeSuccessfulProcess(""); + }), + ); + const client = HttpClient.make((request) => { + if (request.url.endsWith("/.well-known/t3/environment")) { + descriptorRequests += 1; + return Effect.succeed( + HttpClientResponse.fromWeb(request, Response.json({ serverVersion })), + ); + } + return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(""))); + }); + yield* Effect.gen(function* () { + const manager = yield* SshTunnel.SshEnvironmentManager; + yield* manager.ensureEnvironment(target); + assert.equal(descriptorRequests, 0); + for (let attempt = 0; attempt < 2; attempt += 1) { + const paired = yield* manager.ensureEnvironment(target, { issuePairingToken: true }); + assert.equal(paired.pairingToken, `paired-${serverVersion}`); + assert.equal(paired.remoteServerKind, "external"); + } + assert.equal(descriptorRequests, "nodeScriptPath" in runner ? 0 : 2); + assert.equal(launches, 1); + assert.equal(yield* fs.readFileString(launchRunner), launchScript); + }).pipe( + Effect.provide( + Layer.mergeAll( + Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), + Layer.succeed(HttpClient.HttpClient, client), + Layer.succeed(NetService.NetService, testNetService), + SshAuth.SshPasswordPrompt.disabledLayer, + SshTunnel.SshEnvironmentManager.layer({ + resolveCliRunner: Effect.succeed(runner), + }), + ), + ), + Effect.scoped, + ); + }).pipe(Effect.provide(NodeServices.layer), Effect.scoped), + ); + }); + + it.effect.each([ + { status: 500, body: "{}" }, + { status: 200, body: "not JSON" }, + { status: 200, body: "{}" }, + { status: 200, body: '{"serverVersion":"../invalid"}' }, + { status: 200, body: null }, + ])("does not mint a grant when server version discovery fails: %j", ({ status, body }) => { + let pairingCommands = 0; + const spawner = ChildProcessSpawner.make((command) => { + const args = commandArgs(command); + if (args.includes("-N")) return Effect.succeed(makeRunningProcess(() => undefined)); + if (args.includes("--")) return Effect.succeed(makeSuccessfulProcess('{"remotePort":3773}')); + if (args.includes("sh")) pairingCommands += 1; + return Effect.succeed(makeSuccessfulProcess("")); + }); + return Effect.gen(function* () { + const discoveryStarted = yield* Deferred.make(); + const client = HttpClient.make((request) => { + if (request.url.endsWith("/.well-known/t3/environment") && body === null) { + return Deferred.succeed(discoveryStarted, undefined).pipe(Effect.andThen(Effect.never)); + } + return Effect.succeed( + HttpClientResponse.fromWeb( + request, + request.url.endsWith("/.well-known/t3/environment") + ? new Response(body, { status }) + : new Response(""), + ), + ); + }); + const manager = yield* SshTunnel.SshEnvironmentManager; + const pairing = yield* Effect.forkChild( + Effect.result( + manager + .ensureEnvironment( + { alias: "devbox", hostname: "devbox", username: null, port: null }, + { issuePairingToken: true }, + ) + .pipe(Effect.provideService(HttpClient.HttpClient, client)), + ), + ); + if (body === null) { + yield* Deferred.await(discoveryStarted); + yield* TestClock.adjust(Duration.seconds(20)); + } + const result = yield* Fiber.join(pairing); + assert.isTrue(Result.isFailure(result)); + if (Result.isFailure(result)) assert.instanceOf(result.failure, SshPairingError); + assert.equal(pairingCommands, 0); + }).pipe( + Effect.provide( + Layer.mergeAll( + NodeServices.layer, + Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), + Layer.succeed(HttpClient.HttpClient, testHttpClient), + Layer.succeed(NetService.NetService, testNetService), + SshAuth.SshPasswordPrompt.disabledLayer, + SshTunnel.SshEnvironmentManager.layer({ resolveCliRunner: Effect.succeed(ARCHIVE) }), + ), + ), + Effect.scoped, + ); + }); + it.effect.each(["successful stop", "failed stop"] as const)( "closes the tunnel scope and starts fresh after a %s", (mode) => { diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 6d70e381c04a..5f502a9b2bf4 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -21,7 +21,7 @@ import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { HttpClient } from "effect/http"; +import { HttpClient, HttpClientResponse } from "effect/http"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import * as SshAuth from "./auth.ts"; @@ -723,7 +723,7 @@ printf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGE const REMOTE_PAIRING_SCRIPT = `set -eu STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" DEFAULT_SERVER_HOME="$HOME/.t3" -RUNNER_FILE="$STATE_DIR/run-t3.sh" +RUNNER_FILE="$STATE_DIR/pair-t3.sh" mkdir -p "$STATE_DIR" cat >"$RUNNER_FILE" <<'SH' @@T3_RUNNER_SCRIPT@@ @@ -846,6 +846,41 @@ export function buildRemotePairingScript(stateKey: string, input?: RemoteT3Runne }); } +const resolveRemotePairingRunner = Effect.fn("ssh/tunnel.resolveRemotePairingRunner")(function* ( + httpBaseUrl: string, + runner: RemoteT3RunnerOptions | undefined, +) { + if (isNodeScriptRunner(runner)) { + return runner; + } + const descriptor = yield* HttpClient.get( + new URL("/.well-known/t3/environment", httpBaseUrl).toString(), + ).pipe( + Effect.flatMap(HttpClientResponse.filterStatusOk), + Effect.flatMap( + HttpClientResponse.schemaBodyJson(Schema.Struct({ serverVersion: Schema.String })), + ), + Effect.timeout(SSH_READY_TIMEOUT_MS), + Effect.mapError( + (cause) => + new SshPairingError({ + message: "Failed to discover the running SSH server version for pairing.", + stdout: "", + cause, + }), + ), + ); + if (!EXACT_ARCHIVE_VERSION.test(descriptor.serverVersion)) { + return yield* new SshPairingError({ + message: "The running SSH server did not report a valid release version for pairing.", + stdout: "", + }); + } + // A reused server can predate the desktop's runner. Its own CLI must mint + // the grant so scopes and persistence formats agree with the consumer. + return { ...runner, archiveVersion: descriptor.serverVersion }; +}); + export function buildRemoteStopScript(stateKey: string): string { return applyScriptPlaceholders(REMOTE_STOP_SCRIPT, { T3_STATE_KEY: stateKey, @@ -1687,13 +1722,15 @@ const makeSshEnvironmentManager = Effect.fn("ssh/tunnel.SshEnvironmentManager.ma key, Effect.gen(function* () { const entry = yield* ensureTunnelEntry(key, resolvedTarget, runner); - + const pairingRunner = requestOptions?.issuePairingToken + ? yield* resolveRemotePairingRunner(entry.httpBaseUrl, runner) + : runner; const pairingResult = requestOptions?.issuePairingToken ? yield* runWithSshAuth({ key, target: entry.target, operation: (authOptions) => - issueRemotePairingToken(entry.target, authOptions, runner), + issueRemotePairingToken(entry.target, authOptions, pairingRunner), }) : null; const pairingToken = pairingResult?.credential ?? null; From c52a7630e19edd04ea8cf7617298a0115b1478b0 Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Sun, 11 Oct 2026 12:40:15 +0530 Subject: [PATCH 2/5] fix(ssh): use the response module namespace import --- packages/ssh/src/tunnel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 5f502a9b2bf4..30c1c1e314c1 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -21,7 +21,8 @@ import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { HttpClient, HttpClientResponse } from "effect/http"; +import { HttpClient } from "effect/http"; +import * as HttpClientResponse from "effect/http/HttpClientResponse"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import * as SshAuth from "./auth.ts"; From 7598801f20b19108a879da836260f80a856ecde8 Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Sun, 11 Oct 2026 12:45:37 +0530 Subject: [PATCH 3/5] fix(ssh): use the current host platform reference in pairing tests --- packages/ssh/src/tunnel.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 221fad40b725..ea0cf6bfc7ca 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -491,7 +491,7 @@ describe("ssh tunnel scripts", () => { }).pipe(Effect.provide(layerProcess)); }); - describe.skipIf(HostProcessPlatform.defaultValue() === "win32")("SSH release pairing", () => { + describe.skipIf(HostProcess.Platform.defaultValue() === "win32")("SSH release pairing", () => { it.effect.each(["0.0.46-nightly.20261004.2652", ARCHIVE.archiveVersion, "1.2.4", "0.0.0-dev"])( "mints pairing tokens with the running server's CLI version %s", (serverVersion) => From 9d9581e5bcf38795023970b024412d95b380c696 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 07:28:54 +0000 Subject: [PATCH 4/5] test(ssh): focus reused server pairing regressions --- packages/ssh/src/tunnel.test.ts | 39 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 9fd380d17aa8..ba09f8e6fbfc 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -494,14 +494,16 @@ describe("ssh tunnel scripts", () => { }); describe.skipIf(HostProcess.Platform.defaultValue() === "win32")("SSH release pairing", () => { - it.effect.each(["0.0.46-nightly.20261004.2652", ARCHIVE.archiveVersion, "1.2.4", "0.0.0-dev"])( - "mints pairing tokens with the running server's CLI version %s", - (serverVersion) => + it.effect.each(["archive", "nodeScript"] as const)( + "pairs a reused server without changing its launch runner (%s)", + (mode) => Effect.gen(function* () { + const serverVersion = "0.0.46-nightly.20261004.2652"; + const credential = mode === "archive" ? `paired-${serverVersion}` : "paired-dev"; const fs = yield* FileSystem.FileSystem; const localSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-ssh-pairing-" }); - for (const version of new Set([serverVersion, ARCHIVE.archiveVersion])) { + for (const version of [serverVersion, ARCHIVE.archiveVersion]) { const runtime = `${home}/.t3/runtime/versions/${version}`; yield* fs.makeDirectory(runtime, { recursive: true }); yield* fs.writeFileString(`${runtime}/.install-complete`, version); @@ -511,12 +513,11 @@ describe("ssh tunnel scripts", () => { ); yield* fs.chmod(`${runtime}/t3`, 0o700); } - const runner = - serverVersion === "0.0.0-dev" ? { nodeScriptPath: `${home}/dev.mjs` } : ARCHIVE; + const runner = mode === "nodeScript" ? { nodeScriptPath: `${home}/dev.mjs` } : ARCHIVE; if ("nodeScriptPath" in runner) { yield* fs.writeFileString( runner.nodeScriptPath, - `process.stdout.write(JSON.stringify({ credential: "paired-${serverVersion}" }));`, + `process.stdout.write(JSON.stringify({ credential: "paired-dev" }));`, ); } const target = { alias: "devbox", hostname: "devbox", username: null, port: null }; @@ -531,7 +532,7 @@ describe("ssh tunnel scripts", () => { Effect.gen(function* () { const args = commandArgs(command); if (args.includes("-N")) return makeRunningProcess(() => undefined); - if (args.includes("--")) { + if (args.some((arg, index) => arg === "-s" && args[index + 1] === "--")) { launches += 1; return makeSuccessfulProcess('{"remotePort":3773,"serverKind":"external"}\n'); } @@ -567,6 +568,7 @@ describe("ssh tunnel scripts", () => { ); const client = HttpClient.make((request) => { if (request.url.endsWith("/.well-known/t3/environment")) { + assert.equal(request.url, "http://127.0.0.1:41773/.well-known/t3/environment"); descriptorRequests += 1; return Effect.succeed( HttpClientResponse.fromWeb(request, Response.json({ serverVersion })), @@ -580,12 +582,12 @@ describe("ssh tunnel scripts", () => { assert.equal(descriptorRequests, 0); for (let attempt = 0; attempt < 2; attempt += 1) { const paired = yield* manager.ensureEnvironment(target, { issuePairingToken: true }); - assert.equal(paired.pairingToken, `paired-${serverVersion}`); + assert.equal(paired.pairingToken, credential); + assert.equal(yield* fs.readFileString(launchRunner), launchScript); assert.equal(paired.remoteServerKind, "external"); } assert.equal(descriptorRequests, "nodeScriptPath" in runner ? 0 : 2); assert.equal(launches, 1); - assert.equal(yield* fs.readFileString(launchRunner), launchScript); }).pipe( Effect.provide( Layer.mergeAll( @@ -605,17 +607,20 @@ describe("ssh tunnel scripts", () => { }); it.effect.each([ - { status: 500, body: "{}" }, - { status: 200, body: "not JSON" }, - { status: 200, body: "{}" }, - { status: 200, body: '{"serverVersion":"../invalid"}' }, - { status: 200, body: null }, - ])("does not mint a grant when server version discovery fails: %j", ({ status, body }) => { + { + failure: "HTTP error", + status: 500, + body: JSON.stringify({ serverVersion: ARCHIVE.archiveVersion }), + }, + { failure: "invalid version", status: 200, body: '{"serverVersion":"../invalid"}' }, + { failure: "timeout", status: 200, body: null }, + ])("does not mint a grant when server version discovery fails: $failure", ({ status, body }) => { let pairingCommands = 0; const spawner = ChildProcessSpawner.make((command) => { const args = commandArgs(command); if (args.includes("-N")) return Effect.succeed(makeRunningProcess(() => undefined)); - if (args.includes("--")) return Effect.succeed(makeSuccessfulProcess('{"remotePort":3773}')); + if (args.some((arg, index) => arg === "-s" && args[index + 1] === "--")) + return Effect.succeed(makeSuccessfulProcess('{"remotePort":3773}')); if (args.includes("sh")) pairingCommands += 1; return Effect.succeed(makeSuccessfulProcess("")); }); From ac23054c565db5f2e76ad62c112d453ddf61bf5d Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 07:34:27 +0000 Subject: [PATCH 5/5] fix(ssh): scope reused server pairing discovery --- packages/ssh/src/tunnel.ts | 63 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 98fa0eb5146b..fbd54db201b7 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -22,7 +22,7 @@ import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { HttpClient } from "effect/http"; +import * as HttpClient from "effect/http/HttpClient"; import * as HttpClientResponse from "effect/http/HttpClientResponse"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; @@ -167,6 +167,10 @@ const RemotePairingResult = Schema.Struct({ credential: Schema.String, }); +const RemotePairingServerDescriptor = Schema.Struct({ + serverVersion: Schema.String, +}); + const decodeRemoteLaunchResult = Schema.decodeEffect(fromLenientJson(RemoteLaunchResult)); const decodeRemotePairingResult = Schema.decodeEffect(fromLenientJson(RemotePairingResult)); @@ -880,23 +884,23 @@ const resolveRemotePairingRunner = Effect.fn("ssh/tunnel.resolveRemotePairingRun if (isNodeScriptRunner(runner)) { return runner; } - const descriptor = yield* HttpClient.get( - new URL("/.well-known/t3/environment", httpBaseUrl).toString(), - ).pipe( - Effect.flatMap(HttpClientResponse.filterStatusOk), - Effect.flatMap( - HttpClientResponse.schemaBodyJson(Schema.Struct({ serverVersion: Schema.String })), - ), - Effect.timeout(SSH_READY_TIMEOUT_MS), - Effect.mapError( - (cause) => - new SshPairingError({ - message: "Failed to discover the running SSH server version for pairing.", - stdout: "", - cause, - }), - ), - ); + const client = HttpClient.withScope(yield* HttpClient.HttpClient); + const descriptor = yield* client + .get(new URL("/.well-known/t3/environment", httpBaseUrl).toString()) + .pipe( + Effect.flatMap(HttpClientResponse.filterStatusOk), + Effect.flatMap(HttpClientResponse.schemaBodyJson(RemotePairingServerDescriptor)), + Effect.scoped, + Effect.timeout(SSH_READY_TIMEOUT_MS), + Effect.mapError( + (cause) => + new SshPairingError({ + message: "Failed to discover the running SSH server version for pairing.", + stdout: "", + cause, + }), + ), + ); if (!EXACT_ARCHIVE_VERSION.test(descriptor.serverVersion)) { return yield* new SshPairingError({ message: "The running SSH server did not report a valid release version for pairing.", @@ -1764,18 +1768,17 @@ const makeSshEnvironmentManager = Effect.fn("ssh/tunnel.SshEnvironmentManager.ma key, Effect.gen(function* () { const entry = yield* ensureTunnelEntry(key, resolvedTarget, runner); - const pairingRunner = requestOptions?.issuePairingToken - ? yield* resolveRemotePairingRunner(entry.httpBaseUrl, runner) - : runner; - const pairingResult = requestOptions?.issuePairingToken - ? yield* runWithSshAuth({ - key, - target: entry.target, - operation: (authOptions) => - issueRemotePairingToken(entry.target, authOptions, pairingRunner), - }) - : null; - const pairingToken = pairingResult?.credential ?? null; + let pairingToken: string | null = null; + if (requestOptions?.issuePairingToken) { + const pairingRunner = yield* resolveRemotePairingRunner(entry.httpBaseUrl, runner); + const pairingResult = yield* runWithSshAuth({ + key, + target: entry.target, + operation: (authOptions) => + issueRemotePairingToken(entry.target, authOptions, pairingRunner), + }); + pairingToken = pairingResult.credential; + } yield* Effect.logInfo("ssh.environment.ensure.succeeded", { ...sshTargetLogFields(entry.target),