{"record":{"id":"062cd4aef838433f","repo":"JuliusBrussee/caveman","slug":"caveman-agent-memory-recallbudget-must-be-a-non-n","errorCode":null,"errorMessage":"caveman agent: memory recallBudget must be a non-negative integer","messagePattern":"caveman agent: memory recallBudget must be a non-negative integer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":272,"sourceCode":"  const milliseconds = amount * scale;\n  if (!Number.isSafeInteger(amount) || !Number.isSafeInteger(milliseconds) || milliseconds <= 0) {\n    throw new Error(\"cave_memory_ttl_invalid\");\n  }\n  return milliseconds;\n}\n\nexport function memory(options: {\n  namespace: string;\n  provenance?: MemoryDefinition[\"provenance\"];\n  ttl: string;\n  recallBudget: number;\n  consent?: MemoryDefinition[\"consent\"];\n}): MemoryDefinition {\n  if (!/^[a-z0-9][a-z0-9_-]{0,95}$/.test(options.namespace)) {\n    throw new Error(`caveman agent: invalid memory namespace ${JSON.stringify(options.namespace)}`);\n  }\n  if (!Number.isSafeInteger(options.recallBudget) || options.recallBudget < 0) {\n    throw new Error(\"caveman agent: memory recallBudget must be a non-negative integer\");\n  }\n  try {\n    memoryTTLMilliseconds(options.ttl);\n  } catch {\n    throw new Error(\"caveman agent: memory ttl must use positive m, h, or d duration\");\n  }\n  // Fail closed at CONSTRUCTION, not at tool-call time: the durable\n  // store implements only local, single-tenant memory. A `project`/`external`\n  // provenance or a `project_shared` consent is a shared-backend contract this\n  // package does not provide, so it is refused here rather than burning a model\n  // turn to discover a config the framework already knew was unsupported.\n  const provenance = options.provenance ?? \"local\";\n  if (provenance !== \"local\") {\n    throw new Error(\"cave_memory_provenance_unsupported: only \\\"local\\\" memory is supported\");\n  }\n  const consent = options.consent ?? \"local_only\";\n  if (consent !== \"local_only\") {\n    throw new Error(\"cave_memory_consent_unsupported: only \\\"local_only\\\" consent is supported\");","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/primitives.ts#L254-L290","documentation":"The memory() primitive validates that options.recallBudget is a safe, non-negative integer before constructing a MemoryDefinition. recallBudget caps how many memory records a recall call may return, so a fractional, negative, non-numeric, or unsafe (> Number.MAX_SAFE_INTEGER) value is rejected at construction time. Throwing early keeps a malformed budget from silently breaking recall behavior later at tool-call time.","triggerScenarios":"Calling memory() with recallBudget set to a negative number (e.g. -1), a float (e.g. 2.5), a non-number (e.g. a numeric string \"10\" or undefined/null reaching this path), or a value beyond Number.MAX_SAFE_INTEGER. Number.isSafeInteger also rejects NaN and Infinity.","commonSituations":"Reading the budget from an env var or CLI flag without parsing it to a number (it stays a string); computing the budget as a ratio or percentage (fractional result); a typo or swapped variable passing a different negative value; config deserialized from JSON where the field was null.","solutions":["Pass recallBudget as an explicit non-negative integer literal or a value validated with Number.isSafeInteger(budget) && budget >= 0","If the value comes from an env var or input string, coerce with Number(value) and reject NaN before calling memory()","If a fractional budget is intentional, round it explicitly with Math.floor/Math.round and re-check >= 0","If the budget is optional, omit the field or pass a defined default such as 0 rather than undefined/null"],"exampleFix":"// before\nconst budget = process.env.MEMORY_RECALL_BUDGET; // \"20\" (string)\ndefineMemory({ namespace: \"notes\", recallBudget: budget });\n// after\nconst budget = Number(process.env.MEMORY_RECALL_BUDGET);\nif (!Number.isSafeInteger(budget) || budget < 0) {\n  throw new Error(`MEMORY_RECALL_BUDGET must be a non-negative integer, got ${process.env.MEMORY_RECALL_BUDGET}`);\n}\ndefineMemory({ namespace: \"notes\", recallBudget: budget });","handlingStrategy":"validation","validationCode":"function isValidRecallBudget(v) {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0;\n}\n// before calling: if (!isValidRecallBudget(budget)) throw new Error(`bad recallBudget: ${budget}`);","typeGuard":"function isRecallBudget(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0;\n}","tryCatchPattern":"try {\n  const def = memory({ namespace: \"notes\", recallBudget: budget });\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"recallBudget must be a non-negative integer\")) {\n    console.error(`Invalid recallBudget ${JSON.stringify(budget)}: use a non-negative integer`);\n    return;\n  }\n  throw err;\n}","preventionTips":["Coerce env/CLI values with Number() and validate with Number.isSafeInteger before use","Never compute budgets as fractional ratios without rounding","Centralize memory options construction in one validated helper","Add a unit test asserting memory() rejects -1, 2.5, NaN, and \"10\""],"tags":["validation","configuration","typescript","memory"],"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"}