{"record":{"id":"d487802912464e57","repo":"JuliusBrussee/caveman","slug":"cave-memory-agent-invalid","errorCode":"cave_memory_agent_invalid","errorMessage":"cave_memory_agent_invalid","messagePattern":"cave_memory_agent_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/memory-store.ts","lineNumber":61,"sourceCode":"function defaultRoot(): string {\n  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  }","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/memory-store.ts#L43-L79","documentation":"`memoryFilePath` validates `agentId` against `/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/` because it becomes a directory name under the tenant folder. IDs that are empty, start with a digit/hyphen/underscore, contain dots, slashes, or other characters, or exceed 128 characters throw this error at store construction/path resolution time.","triggerScenarios":"Passing `agentId: \"\"`, `agentId: \"1st-agent\"` (starts with digit), `agentId: \"agent/v2\"`, `agentId: \"my.agent\"`, or a UUID containing characters outside `[a-zA-Z0-9_-]` such as braces used in some UUID string formats.","commonSituations":"Generating agent IDs programmatically from hostnames or emails; using uppercase-free numeric IDs auto-generated by another system; concatenating version suffixes with `/` or `.` separators.","solutions":["Ensure the agentId starts with a letter and contains only letters, digits, underscores, and hyphens (max 128 chars), e.g. `\"agent-1\"`.","Sanitize before use: `id.replace(/[^a-zA-Z0-9_-]/g, \"-\").replace(/^[-_0-9]*/, \"\")` and prefix a letter if it starts with a digit, e.g. `\"a\" + id`.","Use a stable, letter-prefixed slug like the agent's registered name instead of a raw machine-generated identifier."],"exampleFix":"// before\ncreateMemoryStore({ agentId: \"01HQ9X-agent\" }); // starts with digit\n// after\ncreateMemoryStore({ agentId: \"agent-01hq9x\" }); // letter-first, allowed charset","handlingStrategy":"validation","validationCode":"const AGENT_RE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;\nfunction safeAgentId(raw) {\n  let id = String(raw).replace(/[^a-zA-Z0-9_-]/g, \"-\").replace(/^[-_0-9]+/, \"\").slice(0, 128);\n  if (!/^[a-zA-Z]/.test(id)) id = \"a\" + id;\n  if (!AGENT_RE.test(id)) throw new TypeError(`agentId invalid after sanitize: ${raw}`);\n  return id;\n}\ncreateMemoryStore({ agentId: safeAgentId(agent.id) });","typeGuard":"const isValidAgentId = (id) => typeof id === \"string\" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(id);","tryCatchPattern":"try {\n  const store = createMemoryStore({ agentId });\n} catch (e) {\n  if (e.message === \"cave_memory_agent_invalid\") {\n    throw new Error(`agentId '${agentId}' must start with a letter and use only [a-zA-Z0-9_-] (<=128)`, { cause: e });\n  }\n  throw e;\n}","preventionTips":["Use letter-prefixed slugs for agent IDs instead of raw UUIDs, hostnames, or emails","Strip characters like `.`, `/`, `:`, and braces before registering agents","Validate agent IDs at registration time so failures never surface mid-run","Remember the ID becomes a directory name — keep it filesystem-safe by design"],"tags":["configuration","validation","identifier-format","path-safety"],"backgroundTag":"invalid-identifier-format","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"}