JuliusBrussee/caveman · error · Error
cave_breaker_threshold_invalid
cave_breaker_threshold_invalid
Error message
cave_breaker_threshold_invalid
What it means
`normalizeRunBreakers` validates the four breaker thresholds — `repeatedToolCalls`, `repeatedToolCallWindowTurns`, `noProgressTurns`, `maxToolCallsPerTurn` — after applying defaults. Each must be a safe integer greater than zero; anything else (zero, negative, fractional, `Infinity`, `NaN`, or a value beyond `Number.MAX_SAFE_INTEGER`) throws `cave_breaker_threshold_invalid`. Defaults are valid, so this error always traces to explicitly supplied `RunOptions.breakers` values.
Source
Thrown at packages/agent/src/breakers.ts:85
const DEFAULT_RETRY_BACKOFF_MS = 250;
export function normalizeRunBreakers(
breakers: RunBreakers,
hasBudget: boolean,
): NormalizedBreakers {
const repeatedToolCalls = breakers.repeatedToolCalls ?? DEFAULT_REPEATED_TOOL_CALLS;
const repeatedToolCallWindowTurns = breakers.repeatedToolCallWindowTurns ??
DEFAULT_REPEATED_TOOL_CALL_WINDOW_TURNS;
const noProgressTurns = breakers.noProgressTurns ?? DEFAULT_NO_PROGRESS_TURNS;
const maxToolCallsPerTurn = breakers.maxToolCallsPerTurn ?? DEFAULT_MAX_TOOL_CALLS_PER_TURN;
for (const value of [
repeatedToolCalls,
repeatedToolCallWindowTurns,
noProgressTurns,
maxToolCallsPerTurn,
]) {
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error("cave_breaker_threshold_invalid");
}
}
if (breakers.retry !== undefined && !hasBudget) {
throw new Error("cave_breaker_retry_requires_budget");
}
if (breakers.retry !== undefined &&
(!Number.isFinite(breakers.retry.maxSpend) || breakers.retry.maxSpend <= 0)) {
throw new Error("cave_breaker_retry_spend_invalid");
}
const retryBackoffMs = breakers.retry?.backoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
if (!Number.isSafeInteger(retryBackoffMs) || retryBackoffMs < 0) {
throw new Error("cave_breaker_retry_backoff_invalid");
}
return Object.freeze({
repeatedToolCalls,
repeatedToolCallWindowTurns,
noProgressTurns,
maxToolCallsPerTurn,View on GitHub (pinned to 27d5a3981a)
Solutions
- Set every supplied threshold to a whole number ≥ 1 (e.g. `repeatedToolCalls: 3`).
- To disable a breaker, omit its field from `RunOptions.breakers` entirely rather than passing 0.
- Coerce and check config values before passing them: `Number.isSafeInteger(v) && v > 0`.
Example fix
// before
run(agent, { breakers: { repeatedToolCalls: 0, noProgressTurns: 2.5 } });
// after
run(agent, { breakers: { repeatedToolCalls: 3, noProgressTurns: 3 } });
// or omit a breaker to leave it at its default Defensive patterns
Strategy: validation
Validate before calling
function isValidBreakerThreshold(v: unknown): v is number {
return Number.isSafeInteger(v) && (v as number) > 0;
}
const thresholds = [breakers.repeatedToolCalls, breakers.noProgressTurns].filter((v) => v !== undefined);
if (!thresholds.every(isValidBreakerThreshold)) throw new Error("breaker thresholds must be positive integers"); Type guard
function isPositiveSafeInteger(v: unknown): v is number {
return Number.isSafeInteger(v) && (v as number) > 0;
} Try / catch
try {
normalizeRunBreakers(breakers, hasBudget);
} catch (err) {
if (err instanceof Error && err.message === "cave_breaker_threshold_invalid") {
// config error: fix the offending threshold; retrying unchanged is pointless
}
} Prevention
- Omit a breaker field to use its default instead of passing 0.
- Validate config-sourced numbers with Number.isSafeInteger before building RunOptions.
- Type breaker config fields as `number` and lint against string env values.
When it happens
Trigger: Setting `breakers: { repeatedToolCalls: 0 }` (perhaps intending "disabled"), `{ noProgressTurns: 2.5 }`, `{ maxToolCallsPerTurn: Infinity }`, or reading thresholds from env/config that yield strings or NaN.
Common situations: Trying to disable a breaker with 0 (breakers are opt-in — omit the field instead); config parsed from CLI flags as strings; computing thresholds dynamically (e.g. `turns * 1.5`) producing fractions; `parseInt(undefined)` yielding NaN.
Related errors
- cave_breaker_retry_backoff_invalid
- caveman agent: subagent maxCalls must be a positive integer
- cave_breaker_retry_requires_budget
- cave_breaker_retry_spend_invalid
- caveman build: config must export defineBuild()
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/98524ce48d12436a.
Report an issue: GitHub.