{"record":{"id":"2e65cff0512f807f","repo":"JuliusBrussee/caveman","slug":"caveman-agent-memory-ttl-must-use-positive-m-h","errorCode":null,"errorMessage":"caveman agent: memory ttl must use positive m, h, or d duration","messagePattern":"caveman agent: memory ttl must use positive m, h, or d duration","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":277,"sourceCode":"}\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\");\n  }\n  return Object.freeze({\n    kind: \"memory\",\n    namespace: options.namespace,\n    provenance,","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/primitives.ts#L259-L295","documentation":"The memory() builder wraps any cave_memory_ttl_invalid failure from memoryTTLMilliseconds() in this clearer message: the ttl option must be a positive integer duration in m, h, or d units. It fires for both failure modes — wrong format (regex miss) and overflow past safe-integer milliseconds — because the builder intentionally re-raises a user-facing message instead of leaking the lower-level code.","triggerScenarios":"Calling memory({ ttl }) with '90s', 'P1D', '1w', '0h', '1.5d', '', '3600', or an overflowing value like '999999999999999d'.","commonSituations":"TTLs authored in ISO-8601 or human form in config ('1 day', 'P1D'); seconds-based durations carried over from cache libraries ('300s'); 'never expires' sentinel overflow values.","solutions":["Express the TTL as '<positive-int>m|h|d', e.g. '30m', '12h', '7d'","Convert other units before calling memory(): seconds -> Math.ceil(s/60) + 'm', days -> d + 'd'","Lint/validate ttl in your config loader against ^([1-9][0-9]*)(m|h|d)$ so mistakes fail at config-parse time"],"exampleFix":"// before\nmemory({ namespace: 'notes', ttl: 'P1D', recallBudget: 10 });\n\n// after\nmemory({ namespace: 'notes', ttl: '1d', recallBudget: 10 });","handlingStrategy":"validation","validationCode":"function toCaveTtl(ms: number): string {\n  if (!Number.isFinite(ms) || ms <= 0) throw new Error('ttl must be positive milliseconds');\n  const units: [number, string][] = [[86_400_000, 'd'], [3_600_000, 'h'], [60_000, 'm']];\n  for (const [scale, suffix] of units) { if (ms % scale === 0) return `${Math.min(ms / scale, 100_000)}${suffix}`; }\n  return `${Math.ceil(ms / 60_000)}m`;\n}","typeGuard":"function isCaveTtl(value: unknown): value is string { return typeof value === 'string' && /^([1-9][0-9]*)(m|h|d)$/.test(value); }","tryCatchPattern":"try { memory(opts); } catch (e) { if (e instanceof Error && e.message.includes('memory ttl must use positive m, h, or d')) throw new ConfigError(`ttl '${opts.ttl}' invalid; expected e.g. '30m'|'12h'|'7d'`, { cause: e }); throw e; }","preventionTips":["Centralize TTL construction in one helper","Forbid seconds/ISO durations in the config schema","Document the m/h/d grammar next to every ttl field in your config reference"],"tags":["validation","memory","ttl","config"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}