{"record":{"id":"e7233c7190bfab88","repo":"JuliusBrussee/caveman","slug":"cave-compaction-option-invalid","errorCode":"cave_compaction_option_invalid","errorMessage":"cave_compaction_option_invalid","messagePattern":"cave_compaction_option_invalid","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/compaction.ts","lineNumber":100,"sourceCode":"  keepRecentTokens: 8_000,\n  summaryMaxTokens: 2_048,\n  minYieldTokens: 20_000,\n  headroomCalls: 3,\n  pinnedUserTokens: 20_000,\n});\n\nexport function normalizeCompaction(options: CompactionOptions = {}): NormalizedCompaction {\n  const merged = {\n    maxCompactions: options.maxCompactions ?? DEFAULTS.maxCompactions,\n    keepRecentTokens: options.keepRecentTokens ?? DEFAULTS.keepRecentTokens,\n    summaryMaxTokens: options.summaryMaxTokens ?? DEFAULTS.summaryMaxTokens,\n    minYieldTokens: options.minYieldTokens ?? DEFAULTS.minYieldTokens,\n    headroomCalls: options.headroomCalls ?? DEFAULTS.headroomCalls,\n    pinnedUserTokens: options.pinnedUserTokens ?? DEFAULTS.pinnedUserTokens,\n  };\n  for (const value of Object.values(merged)) {\n    if (!Number.isSafeInteger(value) || value <= 0) {\n      throw new Error(\"cave_compaction_option_invalid\");\n    }\n  }\n  return Object.freeze({\n    ...merged,\n    summarizerModel: options.summarizerModel,\n  });\n}\n\n/** Version of the summary contract the summarizer is asked to emit. */\nexport const SUMMARY_SCHEMA_VERSION = 1;\n\n/**\n * The sectioned summary the summarizer must produce.\n *\n * Structure rather than prose keeps required fields explicit. `constraintsRestated`\n * is a restatement only — the pinned buffer is the carrier, and a summary that\n * becomes the only carrier reproduces the failure this design exists to avoid.\n */","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/compaction.ts#L82-L118","documentation":"normalizeCompaction merges caller options with defaults, then requires every numeric option (maxCompactions, keepRecentTokens, summaryMaxTokens, minYieldTokens, headroomCalls, pinnedUserTokens) to be a safe positive integer. Any zero, negative, fractional, NaN, Infinity, or non-number value throws immediately.","triggerScenarios":"Passing CompactionOptions with e.g. keepRecentTokens: 0, summaryMaxTokens: 1.5, maxCompactions: -1, or a value parsed from user config as a string ('4096'), or undefined leaking through arithmetic that produced NaN.","commonSituations":"Config files where numbers were quoted; env-var parsing with parseInt returning NaN on bad input; 'disable compaction' attempts expressed as maxCompactions: 0; float math producing fractional token counts.","solutions":["Set every option to a positive whole number: keepRecentTokens: 4096, maxCompactions: 3, etc.","Unquote numbers in config files and coerce parsed strings with Number() before passing, checking Number.isSafeInteger","To effectively disable compaction, use a very large (but valid) budget rather than 0"],"exampleFix":"// before\nnormalizeCompaction({ keepRecentTokens: 0, summaryMaxTokens: 2048.5 });\n\n// after\nnormalizeCompaction({ keepRecentTokens: 1024, summaryMaxTokens: 2048 });","handlingStrategy":"validation","validationCode":"function assertCompactionOptions(o: Record<string, unknown>): void {\n  for (const [k, v] of Object.entries(o)) {\n    if (!Number.isSafeInteger(v) || (v as number) <= 0) {\n      throw new Error(`compaction option ${k} must be a positive safe integer, got ${String(v)}`);\n    }\n  }\n}","typeGuard":"function isPositiveSafeInteger(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v > 0;\n}","tryCatchPattern":null,"preventionTips":["Validate config-sourced numbers with isPositiveSafeInteger before passing them","Keep numeric options unquoted in JSON/YAML config","Do not use 0 to mean 'disabled' — compaction options have no disabled value"],"tags":["config","validation","compaction","numeric"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}