From be1157e6ea9326f54574181f663da8f12cd188e5 Mon Sep 17 00:00:00 2001 From: MarMar Labs Date: Mon, 31 Aug 2026 12:36:16 -0500 Subject: [PATCH 1/2] fix(ssh): give each managed launch a fresh remote server log The managed remote launch appends to server.log, and the readiness failure branch treats whatever is already in that file as this run's output: if [ -s "$LOG_FILE" ]; then tail -n 80 "$LOG_FILE" >&2 else printf 'It wrote nothing to %s, so it exited before producing any output.\n' fi The empty-log arm arrived in #5132 to name the case where the remote server exits without logging anything. The log is opened in append mode and never cleared, so [ -s ] is true from the first run that logs onward. After that the empty-log arm is unreachable, a server that dies silently is reported with the previous run's error, and the user is pointed at the wrong remedy. The file also grows without bound across managed restarts. Unlink rather than truncate. wait_for_pid_exit gives up after two seconds, so a previous server that ignores the kill is still holding its descriptor when the next launch runs. Unlinking leaves it writing into the old file; truncating leaves it writing into the new one, which puts the size back above zero and sends the diagnostic down the tail branch again. The new test runs the real generated script through a shell against a seeded stale log, with a fake node that picks a port, fails readiness at once, and lets the runner exit without writing a byte. It fails on the append-only script at the "It wrote nothing to" assertion. --- packages/ssh/src/tunnel.test.ts | 114 ++++++++++++++++++++++++++++++++ packages/ssh/src/tunnel.ts | 1 + 2 files changed, 115 insertions(+) diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 392885b640c1..f81e028c3083 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -1,4 +1,7 @@ +// @effect-diagnostics nodeBuiltinImport:off - the executed suite runs the generated launch script through a real POSIX shell. import { assert, describe, it } from "@effect/vitest"; +import { afterAll } from "vite-plus/test"; +import * as NodeChildProcess from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as NetService from "@t3tools/shared/Net"; import * as Duration from "effect/Duration"; @@ -447,3 +450,114 @@ describe("ssh tunnel scripts", () => { }).pipe(Effect.provide(layer), Effect.scoped); }); }); + +// The launch script's failure diagnostic only misbehaves when a real shell runs +// the failure branch against a log left over from a previous run, so the suite +// below executes the real generated script. Find a shell that has the tools it +// needs; anywhere else the executed suite skips. +const REQUIRED_SHELL_TOOLS = ["nohup", "mktemp", "cmp", "tail"] as const; + +const posixShellRunner = (() => { + // Candidates rather than a platform switch: wsl.exe simply fails to spawn + // where it does not exist, which is the same answer as a missing tool. + const candidates = [ + { file: "bash", args: [] as ReadonlyArray }, + { file: "wsl.exe", args: ["-e", "bash"] as ReadonlyArray }, + ]; + const probe = REQUIRED_SHELL_TOOLS.map((tool) => `command -v ${tool} >/dev/null || exit 1`).join( + "\n", + ); + return ( + candidates.find((candidate) => { + const result = NodeChildProcess.spawnSync(candidate.file, [...candidate.args, "-c", probe], { + encoding: "utf8", + }); + return result.status === 0; + }) ?? null + ); +})(); + +const runShell = (script: string, args: ReadonlyArray = []) => { + if (posixShellRunner === null) throw new Error("no POSIX shell runner available"); + // The launch script arrives on stdin with the state key as $1 in production too. + const result = NodeChildProcess.spawnSync( + posixShellRunner.file, + [...posixShellRunner.args, "-s", "--", ...args], + { input: script, encoding: "utf8" }, + ); + return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }; +}; + +const sh = (value: string) => `'${value.replaceAll("'", "'\\''")}'`; + +// Reading the generated script proves what it says, not what it does. An +// append-mode launch satisfied every text assertion and still blamed a silent +// server's failure on the previous run's log, because [ -s "$LOG_FILE" ] stayed +// true forever once any run had logged. So run the real script in a throwaway +// HOME seeded with a stale log, with a fake `node` that picks a port, fails +// readiness immediately, and lets the runner exit without writing a byte. +describe.skipIf(posixShellRunner === null)("remote launch script (executed)", () => { + const fixtures: Array = []; + + afterAll(() => { + for (const work of fixtures) runShell(`set -eu\nrm -rf ${sh(work)}`); + fixtures.length = 0; + }); + + it("reports a silent launch instead of tailing the previous run's log", () => { + const setup = runShell( + [ + "set -eu", + "work=$(mktemp -d)", + 'mkdir -p "$work/bin" "$work/home/.t3/ssh-launch/launch-test"', + // One fake node serves the launch script's three contracts, told apart + // by argv: `node - /port ...` picks a port, `node - ` finds no external server, `node - ...` probes readiness, + // and `node