From 8025376ee6b89eca6cbcb12e5bb6eba2eb7755c2 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Sun, 1 Mar 2026 10:48:33 -0800 Subject: [PATCH] fix: use ignore stdin for SSH commands to prevent deadlock on Hetzner and DigitalOcean (#2066) runServer and runServerCapture on Hetzner and DigitalOcean used stdio:["pipe",...] for stdin but called proc.stdin!.end() AFTER await proc.exited. If a remote SSH command reads from stdin (apt prompts, read calls), the process deadlocks until the 5-minute timeout fires. AWS and GCP correctly use stdio:["ignore",...]. Fix: change stdin from "pipe" to "ignore" in runServer and runServerCapture for both Hetzner and DigitalOcean, removing the now-unnecessary stdin.end() calls. Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 --- packages/cli/package.json | 2 +- packages/cli/src/digitalocean/digitalocean.ts | 14 ++------------ packages/cli/src/hetzner/hetzner.ts | 14 ++------------ 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e354f7de..2b4a9968 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.11.19", + "version": "0.11.20", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/cli/src/digitalocean/digitalocean.ts b/packages/cli/src/digitalocean/digitalocean.ts index bf7fcc98..7848be32 100644 --- a/packages/cli/src/digitalocean/digitalocean.ts +++ b/packages/cli/src/digitalocean/digitalocean.ts @@ -995,7 +995,7 @@ export async function runServer(cmd: string, timeoutSecs?: number, ip?: string): ], { stdio: [ - "pipe", + "ignore", "inherit", "inherit", ], @@ -1005,11 +1005,6 @@ export async function runServer(cmd: string, timeoutSecs?: number, ip?: string): const timeout = (timeoutSecs || 300) * 1000; const timer = setTimeout(() => killWithTimeout(proc), timeout); const exitCode = await proc.exited; - try { - proc.stdin!.end(); - } catch { - /* already closed */ - } clearTimeout(timer); if (exitCode !== 0) { @@ -1032,7 +1027,7 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number, ip?: s ], { stdio: [ - "pipe", + "ignore", "pipe", "pipe", ], @@ -1047,11 +1042,6 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number, ip?: s new Response(proc.stderr).text(), ]); const exitCode = await proc.exited; - try { - proc.stdin!.end(); - } catch { - /* already closed */ - } clearTimeout(timer); if (exitCode !== 0) { diff --git a/packages/cli/src/hetzner/hetzner.ts b/packages/cli/src/hetzner/hetzner.ts index 089175ea..7bf4bc1e 100644 --- a/packages/cli/src/hetzner/hetzner.ts +++ b/packages/cli/src/hetzner/hetzner.ts @@ -520,7 +520,7 @@ export async function runServer(cmd: string, timeoutSecs?: number, ip?: string): ], { stdio: [ - "pipe", + "ignore", "inherit", "inherit", ], @@ -530,11 +530,6 @@ export async function runServer(cmd: string, timeoutSecs?: number, ip?: string): const timeout = (timeoutSecs || 300) * 1000; const timer = setTimeout(() => killWithTimeout(proc), timeout); const exitCode = await proc.exited; - try { - proc.stdin!.end(); - } catch { - /* already closed */ - } clearTimeout(timer); if (exitCode !== 0) { @@ -557,7 +552,7 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number, ip?: s ], { stdio: [ - "pipe", + "ignore", "pipe", "pipe", ], @@ -572,11 +567,6 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number, ip?: s new Response(proc.stderr).text(), ]); const exitCode = await proc.exited; - try { - proc.stdin!.end(); - } catch { - /* already closed */ - } clearTimeout(timer); if (exitCode !== 0) {