fix: add timeout protection to uploadFile across all SSH-based clouds (#2298)

All four SSH-based uploadFile functions (Hetzner, DO, AWS, GCP) used
`await proc.exited` on SCP subprocesses without any timeout guard.
If SCP hangs due to a network issue, the CLI hangs indefinitely.

This adds the same killWithTimeout pattern already used by runServer
and runServerCapture in these same files: a 120-second timeout that
kills the SCP process if it stalls.

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-07 13:48:11 -08:00 committed by GitHub
parent 7bebc6558f
commit 1991ffcb15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 32 additions and 12 deletions

View file

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

View file

@ -1140,8 +1140,13 @@ export async function uploadFile(localPath: string, remotePath: string): Promise
],
},
);
if ((await proc.exited) !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
const timer = setTimeout(() => killWithTimeout(proc), 120_000);
try {
if ((await proc.exited) !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
}
} finally {
clearTimeout(timer);
}
}

View file

@ -1126,9 +1126,14 @@ export async function uploadFile(localPath: string, remotePath: string, ip?: str
],
},
);
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
const timer = setTimeout(() => killWithTimeout(proc), 120_000);
try {
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
}
} finally {
clearTimeout(timer);
}
}

View file

@ -948,9 +948,14 @@ export async function uploadFile(localPath: string, remotePath: string): Promise
env: process.env,
},
);
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
const timer = setTimeout(() => killWithTimeout(proc), 120_000);
try {
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
}
} finally {
clearTimeout(timer);
}
}

View file

@ -608,9 +608,14 @@ export async function uploadFile(localPath: string, remotePath: string, ip?: str
],
},
);
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
const timer = setTimeout(() => killWithTimeout(proc), 120_000);
try {
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`upload_file failed for ${remotePath}`);
}
} finally {
clearTimeout(timer);
}
}