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

  1. Omit a knob to use its default, or set a positive whole number (e.g. repeatedToolCalls: 5)
  2. Validate breaker config with Number.isSafeInteger(x) && x > 0 before passing RunOptions
  3. 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

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


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)