{"record":{"id":"8890b7befc3ef6a6","repo":"JuliusBrussee/caveman","slug":"cave-retry-accounting-invalid","errorCode":"cave_retry_accounting_invalid","errorMessage":"cave_retry_accounting_invalid","messagePattern":"cave_retry_accounting_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/breakers.ts","lineNumber":319,"sourceCode":"  /** Settle receipt evidence for one already-recorded retry attempt. */\n  settleRetry(\n    attempt: number,\n    measuredSpend: number,\n    spendBasis: NonNullable<BreakerEvent[\"spendBasis\"]>,\n  ): void {\n    // Attempt numbers restart for each model call. Settle the newest matching\n    // event that has not already been settled, never an earlier call's retry.\n    let event: BreakerEvent | undefined;\n    for (let index = this.events.length - 1; index >= 0; index--) {\n      const candidate = this.events[index]!;\n      if (candidate.kind === \"retry_attempted\" && candidate.count === attempt &&\n          candidate.measuredSpend === undefined) {\n        event = candidate;\n        break;\n      }\n    }\n    if (event === undefined || !Number.isFinite(measuredSpend) || measuredSpend < 0) {\n      throw new Error(\"cave_retry_accounting_invalid\");\n    }\n    (event as { measuredSpend?: number; spendBasis?: BreakerEvent[\"spendBasis\"] }).measuredSpend = measuredSpend;\n    (event as { measuredSpend?: number; spendBasis?: BreakerEvent[\"spendBasis\"] }).spendBasis = spendBasis;\n  }\n\n  /** Deterministic exponential backoff. No jitter: a breaker must be reproducible. */\n  backoffMs(attempt: number): number {\n    return this.config.retryBackoffMs * 2 ** Math.max(0, attempt - 1);\n  }\n}\n\nexport function callSignature(toolName: string, args: unknown): string {\n  return sha256(stableStringify({ tool: toolName, args: args ?? null }));\n}\n","sourceCodeStart":301,"sourceCodeEnd":334,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/breakers.ts#L301-L334","documentation":"settleRetry records the actual spend (measuredSpend) of a retried model call onto a breaker event so budget accounting stays consistent. If no matching candidate event with an undefined measuredSpend exists, or the measuredSpend is not finite or negative, the internal accounting invariant is broken and this error is thrown.","triggerScenarios":"Calling retryModelCall (which reaches settleRetry) where the measured spend of the attempt is negative or NaN/Infinity, or settle is invoked for an event that was never opened / already settled (no candidate with measuredSpend === undefined).","commonSituations":"A provider adapter returning a NaN or negative cost estimate (e.g. zero-token usage divided incorrectly); double-settling the same attempt due to a bug in the retry wrapper; version mismatch where events were recorded differently.","solutions":["Check the provider usage/cost data feeding measuredSpend for NaN or negative values and clamp/validate before the call.","Ensure settleRetry is called exactly once per retry attempt and that the attempt was opened through the same breaker instance.","Upgrade/align the SDK and provider adapter versions so event recording and settlement match."],"exampleFix":"// before\nsettleRetry(measuredSpend: usage.cost) // usage.cost may be NaN\n// after\nconst spend = Number.isFinite(usage.cost) && usage.cost >= 0 ? usage.cost : 0;\nsettleRetry(spend)","handlingStrategy":"try-catch","validationCode":"const spend = Number(usage.cost);\nif (!Number.isFinite(spend) || spend < 0) throw new Error(`bad measuredSpend from provider: ${usage.cost}`);","typeGuard":"const isValidSpend = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v >= 0;","tryCatchPattern":"try { await retryModelCall(...); } catch (e) {\n  if (e.message === 'cave_retry_accounting_invalid') {\n    logger.error('retry accounting invariant broken — check provider usage data and settleRetry call pattern');\n  }\n  throw e;\n}","preventionTips":["Validate provider cost/usage values (finite, >= 0) at the adapter boundary.","Settle each retry attempt exactly once; never re-settle settled events.","Keep the SDK and provider adapter on compatible versions."],"tags":["accounting","retry","internal","budget"],"backgroundTag":"internal-invariant-violation","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}