JuliusBrussee/caveman · error · Error
cave_compaction_option_invalid
cave_compaction_option_invalid
Error message
cave_compaction_option_invalid
What it means
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.
Source
Thrown at packages/agent/src/compaction.ts:100
keepRecentTokens: 8_000,
summaryMaxTokens: 2_048,
minYieldTokens: 20_000,
headroomCalls: 3,
pinnedUserTokens: 20_000,
});
export function normalizeCompaction(options: CompactionOptions = {}): NormalizedCompaction {
const merged = {
maxCompactions: options.maxCompactions ?? DEFAULTS.maxCompactions,
keepRecentTokens: options.keepRecentTokens ?? DEFAULTS.keepRecentTokens,
summaryMaxTokens: options.summaryMaxTokens ?? DEFAULTS.summaryMaxTokens,
minYieldTokens: options.minYieldTokens ?? DEFAULTS.minYieldTokens,
headroomCalls: options.headroomCalls ?? DEFAULTS.headroomCalls,
pinnedUserTokens: options.pinnedUserTokens ?? DEFAULTS.pinnedUserTokens,
};
for (const value of Object.values(merged)) {
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error("cave_compaction_option_invalid");
}
}
return Object.freeze({
...merged,
summarizerModel: options.summarizerModel,
});
}
/** Version of the summary contract the summarizer is asked to emit. */
export const SUMMARY_SCHEMA_VERSION = 1;
/**
* The sectioned summary the summarizer must produce.
*
* Structure rather than prose keeps required fields explicit. `constraintsRestated`
* is a restatement only — the pinned buffer is the carrier, and a summary that
* becomes the only carrier reproduces the failure this design exists to avoid.
*/View on GitHub (pinned to 27d5a3981a)
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
Example fix
// before
normalizeCompaction({ keepRecentTokens: 0, summaryMaxTokens: 2048.5 });
// after
normalizeCompaction({ keepRecentTokens: 1024, summaryMaxTokens: 2048 }); Defensive patterns
Strategy: validation
Validate before calling
function assertCompactionOptions(o: Record<string, unknown>): void {
for (const [k, v] of Object.entries(o)) {
if (!Number.isSafeInteger(v) || (v as number) <= 0) {
throw new Error(`compaction option ${k} must be a positive safe integer, got ${String(v)}`);
}
}
} Type guard
function isPositiveSafeInteger(v: unknown): v is number {
return typeof v === "number" && Number.isSafeInteger(v) && v > 0;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- caveman agent: memory recallBudget must be a non-negative in
- cave_budget_denomination_ambiguous
- cave_budget_max_invalid
- cave_budget_output_floor_invalid
- cave_budget_on_exhausted_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/e7233c7190bfab88.
Report an issue: GitHub.