{"record":{"id":"a265e5bb150e7ade","repo":"JuliusBrussee/caveman","slug":"caveman-agent-output-maxtokens-must-be-a-positive","errorCode":null,"errorMessage":"caveman agent: output maxTokens must be a positive integer","messagePattern":"caveman agent: output maxTokens must be a positive integer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":404,"sourceCode":"    kind: \"artifact\",\n    strategy: options.strategy ?? \"page\",\n    maxInlineTokens,\n    recovery: options.recovery ?? \"exact_ccr\",\n  });\n}\n\nexport interface OutputDefinition<TSchemaValue extends TSchema | undefined = TSchema | undefined> {\n  readonly kind: \"output\";\n  readonly maxTokens: number;\n  readonly schema?: TSchemaValue;\n}\n\nexport function output<T extends TSchema | undefined = undefined>(options: {\n  maxTokens: number;\n  schema?: T;\n}): OutputDefinition<T> {\n  if (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0) {\n    throw new Error(\"caveman agent: output maxTokens must be a positive integer\");\n  }\n  return Object.freeze({\n    kind: \"output\",\n    maxTokens: options.maxTokens,\n    ...(options.schema === undefined ? {} : { schema: options.schema }),\n  }) as OutputDefinition<T>;\n}\n\nexport type QualityGrader =\n  | { type: \"contains\"; fragments: string[] }\n  | { type: \"tool_called\"; tools: string[] }\n  | { type: \"exact_match\"; expected: string }\n  | { type: \"json_schema\"; schema: TSchema };\n\nexport type EvalGuardrail =\n  | { type: \"latency_threshold\"; p95_ms: number }\n  | { type: \"error_rate\"; max: number };\n","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/primitives.ts#L386-L422","documentation":"Thrown by the output() builder when maxTokens is not a safe integer greater than zero. maxTokens is the hard output allowance for the model's final answer, so 0, negatives, fractions, NaN, and numeric strings are invalid — unlike artifact's cap there is no meaningful zero case.","triggerScenarios":"Calling output({ maxTokens: 0 }), -500, 1024.5, NaN, Infinity, or maxTokens: '2000' from unparsed config.","commonSituations":"Deriving maxTokens from a model limit by multiplication and passing the fraction; copying a provider's default that happens to be a string in JSON config; using 0 to mean 'provider default' — here you must supply a positive integer yourself.","solutions":["Pass a positive integer such as maxTokens: 4096","Clamp computed values: maxTokens: Math.max(1, Math.trunc(derived))","Coerce and validate numeric config with Number.isSafeInteger(v) && v > 0 before calling output()"],"exampleFix":"// before\noutput({ maxTokens: Number(cfg.maxOutput) }); // cfg.maxOutput = '2,000'\n\n// after\noutput({ maxTokens: Math.max(1, Math.trunc(Number(String(cfg.maxOutput).replace(/,/g, '')))) });","handlingStrategy":"validation","validationCode":"function toMaxTokens(raw: unknown): number {\n  const n = typeof raw === 'number' ? raw : Number(raw);\n  if (!Number.isSafeInteger(n) || n <= 0) throw new Error(`maxTokens must be a positive integer, got ${String(raw)}`);\n  return n;\n}","typeGuard":"function isOutputMaxTokens(value: unknown): value is number { return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; }","tryCatchPattern":null,"preventionTips":["Define maxTokens as an integer-only field in config schemas","Clamp provider-derived limits with Math.max(1, Math.trunc(v))","There is no zero/default sentinel — always pass a real budget"],"tags":["validation","output","numeric","tokens"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}