authorgravatar for github@torben.idTorben Wetter <github@torben.id> 2026-08-15 14:05:45+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-15 12:05:45+00:00
log9d0f2fc212858290e79710b44c0635a28dd92c2f
tree2c2b145caa5a6cbf2405de2e97ac95686adb6dff
parente9e46972fbabdee0eb39cbd86470a5b5f5f26910
signature Signed by PGP key B5690EEEBB952194

fix(ssh): surface a failed remote t3 install instead of a silent 0-byte server.log (#5132)


2 files changed, 30 insertions(+), 1 deletions(-)

packages/ssh/src/tunnel.test.ts+9
......@@ -108,6 +108,9 @@ describe("ssh tunnel scripts", () => {
108108 assert.include(script, "exec npx --yes 't3@latest' \"$@\"");
109109 assert.include(script, "exec npm exec --yes 't3@latest' -- \"$@\"");
110110 assert.include(script, "could not install 't3@latest'");
111 assert.include(script, "require_installed_t3_cli npx --yes --package 't3@latest'");
112 assert.include(script, "require_installed_t3_cli npm exec --yes --package 't3@latest'");
113 assert.include(script, "npm produced no t3 executable");
111114 assert.include(script, 'prepend_path_if_dir "$HOME/.local/bin"');
112115 assert.include(script, `T3_NODE_ENGINE_RANGE='${TEST_NODE_ENGINE_RANGE}'`);
113116 assert.include(script, "remote_node_satisfies_engine()");
......@@ -140,6 +143,10 @@ describe("ssh tunnel scripts", () => {
140143
141144 assert.include(script, "exec npx --yes 't3@nightly; touch /tmp/t3-owned' \"$@\"");
142145 assert.include(script, "exec npm exec --yes 't3@nightly; touch /tmp/t3-owned' -- \"$@\"");
146 assert.include(
147 script,
148 "require_installed_t3_cli npx --yes --package 't3@nightly; touch /tmp/t3-owned'",
149 );
143150 assert.notInclude(script, "exec npx --yes t3@nightly; touch /tmp/t3-owned");
144151 });
145152
......@@ -185,6 +192,8 @@ describe("ssh tunnel scripts", () => {
185192 assert.notInclude(buildRemoteLaunchScript(), "server-home");
186193 assert.include(buildRemoteLaunchScript(), "Remote T3 server did not become ready");
187194 assert.include(buildRemoteLaunchScript(), 'wait_ready "60000"');
195 assert.include(buildRemoteLaunchScript(), 'if [ -s "$LOG_FILE" ]; then');
196 assert.include(buildRemoteLaunchScript(), "It wrote nothing to %s");
188197 assert.include(buildRemoteLaunchScript({ packageSpec: "t3@nightly" }), "t3@nightly");
189198 assert.include(
190199 buildRemotePairingScript(target),
packages/ssh/src/tunnel.ts+21-1
......@@ -426,10 +426,26 @@ fi
426426if command -v t3 >/dev/null 2>&1; then
427427 exec t3 "$@"
428428fi
429# npm extracts a package before it runs the native builds of its dependencies,
430# so a failed build (t3 depends on node-pty, which needs a C toolchain) leaves
431# the npx cache without a t3 executable. \`npx --yes\` then exits 0 without
432# running anything at all, which the caller only ever sees as a server that
433# never becomes ready. Resolve the CLI once up front so that install failure is
434# reported here, with npm's own output on stderr.
435require_installed_t3_cli() {
436 T3_CLI_PATH="$("$@" -- sh -c 'command -v t3' || true)"
437 if [ -n "$T3_CLI_PATH" ]; then
438 return 0
439 fi
440 printf 'Remote host installed %s but npm produced no t3 executable, which usually means a native dependency (node-pty) failed to build. Install a C toolchain on the remote host (Debian/Ubuntu: build-essential, Fedora/RHEL: gcc-c++ make, macOS: xcode-select --install) and try again.\\n' @@T3_PACKAGE_SPEC@@ >&2
441 return 1
442}
429443if command -v npx >/dev/null 2>&1; then
444 require_installed_t3_cli npx --yes --package @@T3_PACKAGE_SPEC@@ || exit 1
430445 exec npx --yes @@T3_PACKAGE_SPEC@@ "$@"
431446fi
432447if command -v npm >/dev/null 2>&1; then
448 require_installed_t3_cli npm exec --yes --package @@T3_PACKAGE_SPEC@@ || exit 1
433449 exec npm exec --yes @@T3_PACKAGE_SPEC@@ -- "$@"
434450fi
435451printf 'Remote host is missing the t3 CLI and could not install @@T3_PACKAGE_SPEC@@ because node/npm/npx are unavailable on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2
......@@ -581,7 +597,11 @@ if [ -z "$REMOTE_PORT" ]; then
581597 printf 'managed\\n' >"$MANAGED_FILE"
582598 if ! wait_ready "@@T3_READY_TIMEOUT_MS@@"; then
583599 printf 'Remote T3 server did not become ready on 127.0.0.1:%s.\\n' "$REMOTE_PORT" >&2
584 tail -n 80 "$LOG_FILE" >&2 2>/dev/null || true
600 if [ -s "$LOG_FILE" ]; then
601 tail -n 80 "$LOG_FILE" >&2 2>/dev/null || true
602 else
603 printf 'It wrote nothing to %s, so it exited before producing any output.\\n' "$LOG_FILE" >&2
604 fi
585605 kill "$REMOTE_PID" 2>/dev/null || true
586606 wait_for_pid_exit "$REMOTE_PID"
587607 rm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE"