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

  1. Omit timeout for unlimited; the tool has no default timeout
  2. Convert milliseconds to seconds (ms / 1000) before passing
  3. 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

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

Related errors


AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/dde92c28af8e6c01. Report an issue: GitHub.