{"record":{"id":"09a6292c89c56539","repo":"JuliusBrussee/caveman","slug":"cave-memory-ttl-invalid","errorCode":null,"errorMessage":"cave_memory_ttl_invalid","messagePattern":"cave_memory_ttl_invalid","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":251,"sourceCode":"  if (definition.strategy === \"verbatim\") {\n    return definition.recovery === \"exact_ccr\" ? \"exact_ccr\" : \"inline\";\n  }\n  if (definition.strategy === \"json-index\") return \"compress\";\n  return \"page\";\n}\n\nexport interface MemoryDefinition {\n  readonly kind: \"memory\";\n  readonly namespace: string;\n  readonly provenance: \"local\" | \"project\" | \"external\";\n  readonly ttl: string;\n  readonly recallBudget: number;\n  readonly consent: \"local_only\" | \"project_shared\";\n}\n\nexport function memoryTTLMilliseconds(value: string): number {\n  const match = /^([1-9][0-9]*)(m|h|d)$/.exec(value);\n  if (!match) throw new Error(\"cave_memory_ttl_invalid\");\n  const amount = Number(match[1]);\n  const scale = match[2] === \"m\" ? 60_000 : match[2] === \"h\" ? 3_600_000 : 86_400_000;\n  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)}`);","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/primitives.ts#L233-L269","documentation":"Thrown by memoryTTLMilliseconds() when the TTL string does not match the strict duration grammar ^([1-9][0-9]*)(m|h|d)$ — a positive integer amount with a single unit of m (minutes), h (hours), or d (days). Zero-prefixed, fractional, negative, empty, or suffixed-with-other-unit strings are all rejected before any memory definition is created.","triggerScenarios":"Calling memory({ ttl: ... }) or memoryTTLMilliseconds() directly with values like '0m', '1.5h', '30s', '7days', '-1d', '', ' m', or '04h' — anything with a leading zero, a decimal, a non-m/h/d unit, or extra characters.","commonSituations":"Configuring a memory namespace from user or YAML/JSON config where TTLs are written in ISO-8601 ('P1D'), seconds ('3600s'), or human prose ('1 day'); copy-pasting a TTL from another library's duration format (Go, .NET, or dayjs).","solutions":["Use a plain positive integer followed by exactly one of m, h, or d — e.g. '30m', '12h', '7d'","If the TTL arrives in another unit, convert it first (e.g. Math.ceil(minutes) + 'm') and guard against 0/negative results","Validate config-sourced TTLs with the same regex ^([1-9][0-9]*)(m|h|d)$ at load time so the failure surfaces in config parsing, not agent construction"],"exampleFix":"// before\nmemory({ namespace: 'notes', ttl: '3600s', recallBudget: 10 });\n\n// after\nmemory({ namespace: 'notes', ttl: '60m', recallBudget: 10 });","handlingStrategy":"validation","validationCode":"const TTL_RE = /^([1-9][0-9]*)(m|h|d)$/;\nfunction isTTLString(v: unknown): v is string {\n  return typeof v === 'string' && TTL_RE.test(v);\n}","typeGuard":"function isMemoryTTL(value: unknown): value is `${number}${'m'|'h'|'d'}` { return typeof value === 'string' && /^([1-9][0-9]*)(m|h|d)$/.test(value); }","tryCatchPattern":"try { memoryTTLMilliseconds(ttl); } catch (e) { if (e instanceof Error && e.message === 'cave_memory_ttl_invalid') throw new ConfigError(`ttl '${mask(ttl)}' must be like '30m', '12h', or '7d'`, { cause: e }); throw e; }","preventionTips":["Standardize config TTLs on the m/h/d grammar at the config schema level","Reject ISO-8601 durations at the loader boundary with a clear message","Write one shared toCaveTtl(ms: number): string helper and use it everywhere"],"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"}