{"record":{"id":"7cfd5683eb19c628","repo":"JuliusBrussee/caveman","slug":"cave-memory-namespace-invalid","errorCode":"cave_memory_namespace_invalid","errorMessage":"cave_memory_namespace_invalid","messagePattern":"cave_memory_namespace_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/memory-store.ts","lineNumber":62,"sourceCode":"  return process.env.CAVE_AGENT_MEMORY_ROOT ?? join(homedir(), \".caveman\", \"agent-memory\");\n}\n\n/**\n * The durable file for (tenant, agentId, namespace). The three scoping\n * components are validated to a `[a-z0-9_-]`-class charset with no `.` or path\n * separator, so no component can traverse out of the memory root.\n */\nexport function memoryFilePath(\n  config: MemoryStoreConfig | undefined,\n  agentId: string,\n  namespace: string,\n): string {\n  const tenant = config?.tenant ?? \"_\";\n  if (tenant !== \"_\" && !TENANT_PATTERN.test(tenant)) {\n    throw new Error(\"cave_memory_tenant_invalid\");\n  }\n  if (!AGENT_PATTERN.test(agentId)) throw new Error(\"cave_memory_agent_invalid\");\n  if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(\"cave_memory_namespace_invalid\");\n  return join(config?.root ?? defaultRoot(), tenant, agentId, `${namespace}.json`);\n}\n\nfunction isMemoryEntry(value: unknown): value is MemoryEntry {\n  return value !== null && typeof value === \"object\" &&\n    typeof (value as { text?: unknown }).text === \"string\" &&\n    Number.isSafeInteger((value as { createdAt?: unknown }).createdAt);\n}\n\n/** Read the durable entries. A missing or corrupt file is an empty store, never a throw into a run. */\nexport async function readMemories(filePath: string): Promise<MemoryEntry[]> {\n  try {\n    const parsed: unknown = JSON.parse(await readFile(filePath, \"utf8\"));\n    return Array.isArray(parsed) ? parsed.filter(isMemoryEntry) : [];\n  } catch {\n    return [];\n  }\n}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/memory-store.ts#L44-L80","documentation":"Thrown by memoryFilePath() when namespace fails NAMESPACE_PATTERN /^[a-z0-9][a-z0-9_-]{0,95}$/. This is the strictest of the three patterns: lowercase only (no /i flag), must start with a lowercase letter or digit, only letters/digits/underscore/hyphen afterwards, and a 96-character cap. The namespace names the JSON file (<namespace>.json) inside the tenant/agent directory, so the charset prevents both traversal and filename tricks.","triggerScenarios":"namespace \"Profile\" (uppercase rejected), \"profile.v2\" (dot), \"default/main\" (slash), \"-notes\" (leading hyphen), an empty string, or a namespace longer than 96 characters.","commonSituations":"camelCase or PascalCase namespace constants (\"longTermMemory\"); namespaces mirroring file names including extensions; copy-pasting a namespace pattern from the tenant code where uppercase was allowed.","solutions":["Use a lowercase slug: \"long-term-memory\", \"scratch\", \"facts\".","Normalize before use: ns.toLowerCase().replace(/[^a-z0-9_-]/g, \"-\").slice(0, 96).","Check the 96-character cap when generating namespaced keys programmatically."],"exampleFix":"// before\nmemoryFilePath(config, agentId, \"LongTermMemory\");\n\n// after\nmemoryFilePath(config, agentId, \"long-term-memory\");","handlingStrategy":"validation","validationCode":"const NAMESPACE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,95}$/;\nif (!NAMESPACE_PATTERN.test(namespace)) {\n  throw new TypeError(\"namespace must be lowercase [a-z0-9_-], start alphanumeric, max 96 chars\");\n}","typeGuard":"function isValidNamespace(ns: string): boolean {\n  return /^[a-z0-9][a-z0-9_-]{0,95}$/.test(ns);\n}","tryCatchPattern":null,"preventionTips":["Namespace is lowercase-only — normalize camelCase constants with .toLowerCase().","Ban dots (file extensions) and slashes in namespace constants.","Check the 96-character cap when generating namespaced keys."],"tags":["validation","memory","namespace","path-safety"],"backgroundTag":"invalid-identifier","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}