withastro/astro · error · Error
Timeout
Error message
Timeout
What it means
Thrown by the `shell()` helper in the upgrade tool after the spawned child process has emitted `close` but Node reports `exitCode === null`. In the upgrade tool, `exitCode` is null when the process was killed by the `timeout` option passed to `spawn` (a SIGTERM kill leaves exitCode null). It signals the subprocess did not finish within the configured timeout window.
Source
Thrown at packages/upgrade/src/shell.ts:72
try {
const [resolvedCommand, resolvedFlags] = resolveCommand(command, flags);
child = spawn(resolvedCommand, resolvedFlags, {
cwd: opts.cwd,
stdio: opts.stdio,
timeout: opts.timeout,
});
const done = new Promise<void>((resolve, reject) => {
child.once('error', reject);
child.once('close', () => resolve());
});
[stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr), done]);
} catch (e) {
const message = e instanceof Error ? e.message : stderr || 'Unknown error';
throw new Error(message);
}
const { exitCode } = child;
if (exitCode === null) {
throw new Error('Timeout');
}
if (exitCode !== 0) {
throw new Error(stderr || stdout || `Process exited with code ${exitCode}`);
}
return { stdout, stderr, exitCode };
}
View on GitHub (pinned to d081033d5f)
Solutions
- Increase the timeout: the caller passes `opts.timeout` to `shell()` — raise it (or remove it) for long-running install commands.
- Pre-warm the install cache (`pnpm fetch` or `npm ci` against a lockfile) so the spawned install is faster.
- Fix the registry/mirror to a faster endpoint and ensure network is stable.
- If the spawned command prompts, run it manually once to satisfy the prompt, or pass non-interactive flags (e.g. `--yes`, `--no-interactivity`).
Example fix
// before
child = spawn(resolvedCommand, resolvedFlags, {
cwd: opts.cwd,
stdio: opts.stdio,
timeout: opts.timeout,
});
// after — propagate a richer signal so callers know it was a timeout
if (exitCode === null) {
throw new Error(`Timeout after ${opts.timeout ?? 'n/a'}ms running ${command}`);
} Defensive patterns
Strategy: retry
Validate before calling
// Choose a timeout proportional to the command's expected cost
function timeoutForCommand(cmd: string): number | undefined {
if (/install|fetch/i.test(cmd)) return 10 * 60 * 1000; // 10 min for installs
return 60_000;
}
// pass shell(cmd, flags, { timeout: timeoutForCommand(cmd) }) Type guard
function isTimeoutError(e: unknown): boolean {
return e instanceof Error && e.message === 'Timeout';
} Try / catch
try {
await shell(cmd, flags, { timeout });
} catch (e) {
if (isTimeoutError(e)) {
// retry once with a larger timeout or surface a clear 'install timed out' message
} else throw e;
} Prevention
- Pre-warm caches (`pnpm fetch`) so spawned installs finish within the timeout.
- Use non-interactive flags so subprocesses never block on prompts.
- Pass timeouts proportional to the command's expected runtime.
- Run installs against a fast/local registry mirror.
When it happens
Trigger: A command invoked via `shell()` (e.g. a package manager install/git fetch) takes longer than the `opts.timeout` value passed to spawn; the process hangs on a prompt or network stall and gets killed; on Windows the .exe shim hangs.
Common situations: Slow or metered network during `pnpm install`/`npm install` run by the upgrade command; a global npm/pnpm mirror with high latency; the spawned command is waiting on interactive input that never arrives; a very large dependency tree install.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/141020721c81003d.
Report an issue: GitHub.