earendil-works/pi · error · Error
Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds
Error message
Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds What it means
validateTimeout caps the bash tool's timeout at MAX_TIMEOUT_SECONDS = 2147483.647 (bash.ts:8), which is 2^31-1 milliseconds expressed in seconds, the largest value the underlying setTimeout can schedule. Anything larger throws immediately rather than silently overflowing the timer.
Source
Thrown at packages/agent/src/harness/tools/bash.ts:47
export type BashPrepare<TContext extends ExecutionToolContext = ExecutionToolContext> = (
execution: BashExecution,
context: TContext,
signal?: AbortSignal,
) => void | Promise<void>;
export interface BashToolOptions<TContext extends ExecutionToolContext = ExecutionToolContext> {
commandPrefix?: string;
prepare?: BashPrepare<TContext>;
}
function validateTimeout(timeout: number | undefined): void {
if (timeout === undefined) return;
if (!Number.isFinite(timeout) || timeout <= 0) {
throw new Error("Invalid timeout: must be a finite number of seconds");
}
if (timeout > MAX_TIMEOUT_SECONDS) {
throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);
}
}
export function createBashTool<TContext extends ExecutionToolContext = ExecutionToolContext>(
options?: BashToolOptions<TContext>,
): AgentHarnessTool<TContext, typeof bashSchema, BashToolDetails | undefined> {
return {
name: "bash",
label: "bash",
description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
parameters: bashSchema,
async execute(_toolCallId, { command, timeout }, signal, onUpdate, context) {
validateTimeout(timeout);
const { env } = context;
const execution: BashExecution = {
command: options?.commandPrefix ? `${options.commandPrefix}\n${command}` : command,
cwd: env.cwd,
env: {},View on GitHub (pinned to 4af9d21d3b)
Solutions
- Omit timeout for unlimited; the tool has no default timeout
- Convert milliseconds to seconds (ms / 1000) before passing
- Cap at a realistic value, for example 3600 for one hour
Example fix
// before
await bashTool.execute(id, { command, timeout: 60 * 60 * 1000 }); // ms value -> throws
// after
await bashTool.execute(id, { command, timeout: 3600 }); // seconds Defensive patterns
Strategy: validation
Validate before calling
const MAX_TIMEOUT_SECONDS = 2_147_483_647 / 1000; // 2147483.647
const timeoutSec =
rawMs === undefined ? undefined : Math.min(rawMs / 1000, MAX_TIMEOUT_SECONDS);
await bashTool.execute(id, { command, timeout: timeoutSec }); Prevention
- Omit the field for unlimited runs; there is no default timeout
- Never pass raw millisecond values
- Cap human-scale budgets at sane second values such as 3600
When it happens
Trigger: timeout: 3600000 intending 'one hour' but actually passing 3.6 million seconds; passing Number.MAX_SAFE_INTEGER or another huge sentinel for 'forever'; day-scale arithmetic producing multi-million second counts.
Common situations: Millisecond/second confusion, since the field is seconds; using a very large number to mean 'no limit' instead of omitting the field entirely.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/dde92c28af8e6c01.
Report an issue: GitHub.