JuliusBrussee/caveman · error
caveman agent: tool timeoutMs must be a positive integer
Error message
caveman agent: tool timeoutMs must be a positive integer
What it means
tool() defaults timeoutMs to 30_000 and requires it to be a positive safe integer (Number.isSafeInteger and > 0). Zero, negative, fractional, NaN, or values beyond the safe-integer range are rejected so the scheduler always gets a usable timeout.
Solutions
- Pass a positive integer in milliseconds, e.g. timeoutMs: 60000
- Convert duration values properly (seconds * 1000, Math.round) before passing
- Omit timeoutMs to use the 30-second default
Example fix
// before
tool({ name: "bash", timeoutMs: Number(process.env.TOOL_TIMEOUT), ... }) // "30" ok, "" -> NaN
// after
const timeoutMs = Number.parseInt(process.env.TOOL_TIMEOUT ?? "30000", 10);
tool({ name: "bash", timeoutMs: Number.isSafeInteger(timeoutMs) && timeoutMs > 0 ? timeoutMs : 30000, ... }) Defensive patterns
Strategy: validation
Validate before calling
function validTimeoutMs(v: unknown): v is number {
return typeof v === "number" && Number.isSafeInteger(v) && v > 0;
} Type guard
function isPositiveInt(v: unknown): v is number {
return Number.isSafeInteger(v) && (v as number) > 0;
} Try / catch
try {
const def = tool(options);
} catch (err) {
if (err instanceof Error && err.message.includes("timeoutMs must be a positive integer")) {
// rebuild options with timeoutMs: 30000
} else throw err;
} Prevention
- Convert duration configs to integer milliseconds with Math.round(seconds * 1000)
- Never pass NaN/Infinity — guard Number(...) results with Number.isSafeInteger
- Rely on the 30s default when no explicit timeout is needed
When it happens
Trigger: tool({ timeoutMs: 0 }), tool({ timeoutMs: -1000 }), tool({ timeoutMs: 1.5 }), tool({ timeoutMs: Number.NaN }), or parsing a timeout from config into a non-integer (e.g. "30s" passed through Number()).
Common situations: Duration strings or seconds-based values from config not converted to integer milliseconds; arithmetic producing floats (30 * 0.5); environment placeholders expanding to empty/garbage; infinity from misconfigured defaults.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- at least one report path and the canonical span fixture…
- cave_budget_release_reason_required
- cave_run_deadline_invalid
- cave_subagent_arguments_invalid
- cave_tool_input_schema_mismatch
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/3f06669c4680dbf1.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/primitives.ts:142
options: ToolOptions<TSchema, unknown> |
StandardToolOptions<unknown, unknown, unknown> |
StandardJSONToolOptions<unknown, unknown, unknown>,
): ToolDefinition<unknown, unknown> {
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(options.name)) {
throw new Error(`caveman agent: invalid tool name ${JSON.stringify(options.name)}`);
}
if (!["read", "write", "idempotent", "external"].includes(options.effect)) {
throw new Error(`caveman agent: unknown tool effect ${JSON.stringify(options.effect)}`);
}
const result = typeof options.result === "object"
? artifactResultPolicy(options.result)
: options.result ?? "auto";
if (!["auto", "inline", "page", "compress", "exact_ccr"].includes(result)) {
throw new Error(`caveman agent: unknown tool result policy ${JSON.stringify(result)}`);
}
const timeoutMs = options.timeoutMs ?? 30_000;
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
throw new Error("caveman agent: tool timeoutMs must be a positive integer");
}
const standard = standardToolSchema(options.input);
let input: TSchema;
if (standard === undefined) {
input = options.input as TSchema;
} else {
let converted = "inputJSONSchema" in options
? options.inputJSONSchema
: undefined;
if (converted === undefined && standard.jsonSchema !== undefined) {
try {
converted = standard.jsonSchema.input({ target: "draft-07" });
} catch (error) {
throw new Error("caveman agent: Standard Schema cannot emit draft-07 input JSON Schema", {
cause: error,
});
}
}View on GitHub (pinned to 3ee70a1026)