JuliusBrussee/caveman · error · Error
cave_memory_agent_invalid
Error message
cave_memory_agent_invalid
What it means
memoryFilePath() validates the agentId component of the durable memory path against a strict [a-z0-9_-]-class charset (no dots, no separators) to make path traversal from agentId impossible. An agentId with uppercase letters, slashes, "..", extensions, or spaces throws cave_memory_agent_invalid before any filesystem path is built.
Source
Thrown at packages/agent/src/memory-store.ts:61
function defaultRoot(): string {
return process.env.CAVE_AGENT_MEMORY_ROOT ?? join(homedir(), ".caveman", "agent-memory");
}
/**
* The durable file for (tenant, agentId, namespace). The three scoping
* components are validated to a `[a-z0-9_-]`-class charset with no `.` or path
* separator, so no component can traverse out of the memory root.
*/
export function memoryFilePath(
config: MemoryStoreConfig | undefined,
agentId: string,
namespace: string,
): string {
const tenant = config?.tenant ?? "_";
if (tenant !== "_" && !TENANT_PATTERN.test(tenant)) {
throw new Error("cave_memory_tenant_invalid");
}
if (!AGENT_PATTERN.test(agentId)) throw new Error("cave_memory_agent_invalid");
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error("cave_memory_namespace_invalid");
return join(config?.root ?? defaultRoot(), tenant, agentId, `${namespace}.json`);
}
function isMemoryEntry(value: unknown): value is MemoryEntry {
return value !== null && typeof value === "object" &&
typeof (value as { text?: unknown }).text === "string" &&
Number.isSafeInteger((value as { createdAt?: unknown }).createdAt);
}
/** Read the durable entries. A missing or corrupt file is an empty store, never a throw into a run. */
export async function readMemories(filePath: string): Promise<MemoryEntry[]> {
try {
const parsed: unknown = JSON.parse(await readFile(filePath, "utf8"));
return Array.isArray(parsed) ? parsed.filter(isMemoryEntry) : [];
} catch {
return [];
}View on GitHub (pinned to 27d5a3981a)
Solutions
- Generate agent ids from a slug or kebab-case convention at creation time
- Normalize before storage: agentId.toLowerCase().replace(/[^a-z0-9_-]+/g, "-")
- Lowercase UUIDs: crypto.randomUUID() is already lowercase-safe
- Reject invalid ids at your own API boundary so the store never sees them
Example fix
// before await store.append(agent.name, "notes", entry); // agent.name = "Research Agent" // after const agentId = agent.name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-"); await store.append(agentId, "notes", entry);
Defensive patterns
Strategy: validation
Validate before calling
const AGENT_RE = /^[a-z0-9_-]+$/;
function agentIdFrom(name: string): string {
const id = name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
if (!AGENT_RE.test(id)) throw new Error(`cannot derive safe agent id from ${JSON.stringify(name)}`);
return id;
} Type guard
const isSafeAgentId = (id: string): boolean => /^[a-z0-9_-]+$/.test(id);
Prevention
- Generate agent ids once at creation (slug or lowercase UUID) and store them as the canonical key
- Never use display names or hierarchical paths ("team/agent") as memory keys
- Add a repository-level lint or test asserting all agent ids match the charset
When it happens
Trigger: Calling memory read/write helpers with agentId values like "Research Agent", "../shared", "agent.v2", "", or "AGENT-1" (uppercase). The check applies to every memoryFilePath() call, i.e. every durable memory operation for that agent.
Common situations: Generating agent ids from free-text names or UUIDs with uppercase hex, reusing human-readable agent names as storage keys, or splitting agent ids on characters ("team/agent") that the pattern forbids.
Related errors
- cave_memory_tenant_invalid
- invalid Claude organization id
- cave_live_eval_sandbox_profile_escapes_root
- caveman-code: path escapes the workspace: ${candidate}
- caveman agent: file source escapes project root
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/d487802912464e57.
Report an issue: GitHub.