{"record":{"id":"3f06669c4680dbf1","repo":"JuliusBrussee/caveman","slug":"caveman-agent-tool-timeoutms-must-be-a-positive-i","errorCode":null,"errorMessage":"caveman agent: tool timeoutMs must be a positive integer","messagePattern":"caveman agent: tool timeoutMs must be a positive integer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":142,"sourceCode":"  options: ToolOptions<TSchema, unknown> |\n    StandardToolOptions<unknown, unknown, unknown> |\n    StandardJSONToolOptions<unknown, unknown, unknown>,\n): ToolDefinition<unknown, unknown> {\n  if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(options.name)) {\n    throw new Error(`caveman agent: invalid tool name ${JSON.stringify(options.name)}`);\n  }\n  if (![\"read\", \"write\", \"idempotent\", \"external\"].includes(options.effect)) {\n    throw new Error(`caveman agent: unknown tool effect ${JSON.stringify(options.effect)}`);\n  }\n  const result = typeof options.result === \"object\"\n    ? artifactResultPolicy(options.result)\n    : options.result ?? \"auto\";\n  if (![\"auto\", \"inline\", \"page\", \"compress\", \"exact_ccr\"].includes(result)) {\n    throw new Error(`caveman agent: unknown tool result policy ${JSON.stringify(result)}`);\n  }\n  const timeoutMs = options.timeoutMs ?? 30_000;\n  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {\n    throw new Error(\"caveman agent: tool timeoutMs must be a positive integer\");\n  }\n  const standard = standardToolSchema(options.input);\n  let input: TSchema;\n  if (standard === undefined) {\n    input = options.input as TSchema;\n  } else {\n    let converted = \"inputJSONSchema\" in options\n      ? options.inputJSONSchema\n      : undefined;\n    if (converted === undefined && standard.jsonSchema !== undefined) {\n      try {\n        converted = standard.jsonSchema.input({ target: \"draft-07\" });\n      } catch (error) {\n        throw new Error(\"caveman agent: Standard Schema cannot emit draft-07 input JSON Schema\", {\n          cause: error,\n        });\n      }\n    }","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/primitives.ts#L124-L160","documentation":"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.","triggerScenarios":"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()).","commonSituations":"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.","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"],"exampleFix":"// before\ntool({ name: \"bash\", timeoutMs: Number(process.env.TOOL_TIMEOUT), ... }) // \"30\" ok, \"\" -> NaN\n// after\nconst timeoutMs = Number.parseInt(process.env.TOOL_TIMEOUT ?? \"30000\", 10);\ntool({ name: \"bash\", timeoutMs: Number.isSafeInteger(timeoutMs) && timeoutMs > 0 ? timeoutMs : 30000, ... })","handlingStrategy":"validation","validationCode":"function validTimeoutMs(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v > 0;\n}","typeGuard":"function isPositiveInt(v: unknown): v is number {\n  return Number.isSafeInteger(v) && (v as number) > 0;\n}","tryCatchPattern":"try {\n  const def = tool(options);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"timeoutMs must be a positive integer\")) {\n    // rebuild options with timeoutMs: 30000\n  } else throw err;\n}","preventionTips":["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"],"tags":["validation","timeout","arguments"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}