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 integer breaker knobs after defaults are applied: repeatedToolCalls (default 3), repeatedToolCallWindowTurns (8), noProgressTurns (3), maxToolCallsPerTurn (8). Each must be a positive safe integer; zero, negative, fractional, NaN, or Infinity values throw cave_breaker_threshold_invalid.
Solutions
- Omit a knob to use its default, or set a positive whole number (e.g. repeatedToolCalls: 5)
- Validate breaker config with Number.isSafeInteger(x) && x > 0 before passing RunOptions
- Parse config numbers once at startup and fail fast on invalid values
Example fix
// before
run(agent, { breakers: { repeatedToolCalls: 0, noProgressTurns: 2.5 } });
// after
run(agent, { breakers: { repeatedToolCalls: 5, noProgressTurns: 3 } }); Defensive patterns
Strategy: validation
Validate before calling
const knobs = [breakers.repeatedToolCalls, breakers.repeatedToolCallWindowTurns,
breakers.noProgressTurns, breakers.maxToolCallsPerTurn];
if (knobs.some((k) => k !== undefined && (!Number.isSafeInteger(k) || k <= 0))) {
throw new Error("breaker thresholds must be positive safe integers");
} Type guard
function isPositiveSafeInteger(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) > 0;
} Try / catch
try {
await run(agent, { breakers });
} catch (error) {
if (error instanceof Error && error.message === "cave_breaker_threshold_invalid") {
// One of the four thresholds is not a positive whole number —
// fix or omit the offending knob and restart the run.
}
throw error;
} Prevention
- Validate breaker config from env/JSON once at startup with Number.isSafeInteger
- To use a default, omit the knob instead of passing 0
- Never copy threshold values from tools with different semantics
When it happens
Trigger: RunOptions.breakers set to { repeatedToolCalls: 0 }, { noProgressTurns: 2.5 }, { maxToolCallsPerTurn: Number.POSITIVE_INFINITY }, or a NaN leaking in from parsed config — normalizeRunBreakers rejects it before the run starts.
Common situations: Loading breaker thresholds from env vars/JSON config without numeric validation; copying permissive values from another tool; trying to disable a breaker with 0 instead of omitting the field.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- cave_breaker_retry_backoff_invalid
- cave_breaker_retry_requires_budget
- cave_breaker_retry_spend_invalid
- cave_budget_denomination_ambiguous
- cave_budget_initial_invalid
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/98524ce48d12436a.
Report an issue: GitHub.
Appendix: 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 766dce6b13)