{"record":{"id":"30b6ae8ad442806f","repo":"JuliusBrussee/caveman","slug":"cave-field-invalid","errorCode":"cave_${field}_invalid","errorMessage":"cave_${field}_invalid","messagePattern":"cave_(.+?)_invalid","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1460,"sourceCode":"    let modelCalls = 0;\n    let compactionsUsed = 0;\n    // Incremented the moment a compaction takes a reservation, so a paid\n    // attempt counts even when its summary is later discarded.\n    let compactionsSpent = 0;\n    let previousSummary: ContextSummary | undefined;\n    // Provider-reported, never assumed: the last call either read a cached\n    // prefix or it did not. Before the first call there is nothing to report.\n    let lastCallCacheState: \"warm\" | \"cold\" | \"unknown\" = \"unknown\";\n    const toolCalls: string[] = [];\n    let turnStateChanged = false;\n    const startedAt = performance.now();\n    // Caller-supplied ceilings override the derived defaults. They\n    // fail closed: a non-positive or non-integer value is a malformed cap, not\n    // \"no cap\", so it is rejected before the first provider call.\n    const callCeilingOverride = (value: number | undefined, field: string): number | undefined => {\n      if (value === undefined) return undefined;\n      if (!Number.isInteger(value) || value < 1) {\n        throw new Error(`cave_${field}_invalid`);\n      }\n      return value;\n    };\n    const maxModelCalls = callCeilingOverride(options.maxModelCalls, \"max_model_calls\") ??\n      (efficiencyPlan === undefined\n        ? 64\n        : 1 + Math.max(1, Math.ceil(efficiencyPlan.budgets.retry_cascade_reserve / 256)));\n    const maxToolCalls = callCeilingOverride(options.maxToolCalls, \"max_tool_calls\") ??\n      (efficiencyPlan === undefined\n        ? 64\n        : Math.max(1, definition.tools.length) * Math.max(1, maxModelCalls - 1));\n    const accountedAssistantMessages = new WeakSet<object>();\n    const accountProviderMessage = (message: AssistantMessage): void => {\n      // beforeToolCall runs after the provider message but before its tools.\n      // Account there so a call that consumed the deadline or wallet cannot\n      // launch fresh side effects. turn_end calls this too for tool-free turns;\n      // object identity makes the operation exactly once on either path.\n      if (accountedAssistantMessages.has(message)) {","sourceCodeStart":1442,"sourceCodeEnd":1478,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L1442-L1478","documentation":"Template error thrown by the callCeilingOverride validator when a caller-supplied ceiling (e.g. maxModelCalls) is not a positive integer. The library treats a malformed cap as fail-closed data rather than \"no cap\", rejecting before any provider call.","triggerScenarios":"Passing options.maxModelCalls (or any validated ceiling field) as 0, a negative number, a float (e.g. 1.5), or a non-integer value to streamAgent.","commonSituations":"Reading the cap from env/config as a string-ish or computed value, dividing to compute a cap, or defaulting with 0 intending \"unlimited\".","solutions":["Pass a positive integer (>= 1) for the ceiling option","Coerce/round the value before calling: Math.max(1, Math.round(value))","Omit the option to use the derived default (64 without an efficiency plan)"],"exampleFix":"// before\nstreamAgent({ maxModelCalls: 0 });\n// after\nstreamAgent({ maxModelCalls: Math.max(1, Math.round(userCap)) });","handlingStrategy":"validation","validationCode":"function assertCeiling(v) { if (v !== undefined && (!Number.isInteger(v) || v < 1)) throw new Error(`invalid ceiling: ${v}`); }\nassertCeiling(opts.maxModelCalls);","typeGuard":"const isValidCeiling = (v: unknown): v is number => typeof v === \"number\" && Number.isInteger(v) && v >= 1;","tryCatchPattern":"try { await streamAgent(opts); } catch (e) { if (/^cave_.*_invalid$/.test(e.message)) { /* fix the named ceiling option */ } else throw e; }","preventionTips":["Validate numeric options come from parsed integers, not raw strings","Never use 0 to mean \"unlimited\" — omit the option instead","Round/normalize computed caps before passing them"],"tags":["validation","budget","integer","options"],"backgroundTag":"invalid-argument-value","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"}