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 <noreply@anthropic.com>
This commit is contained in:
A 2026-03-01 10:48:33 -08:00 committed by GitHub
parent 862030b776
commit 8025376ee6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 5 additions and 25 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.11.19",
"version": "0.11.20",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -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) {

View file

@ -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) {