JuliusBrussee/caveman · error
cave_memory_agent_invalid
cave_memory_agent_invalid
Error message
cave_memory_agent_invalid
What it means
`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.
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.
Example fix
// before
createMemoryStore({ agentId: "01HQ9X-agent" }); // starts with digit
// after
createMemoryStore({ agentId: "agent-01hq9x" }); // letter-first, allowed charset Defensive patterns
Strategy: validation
Validate before calling
const AGENT_RE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
function safeAgentId(raw) {
let id = String(raw).replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^[-_0-9]+/, "").slice(0, 128);
if (!/^[a-zA-Z]/.test(id)) id = "a" + id;
if (!AGENT_RE.test(id)) throw new TypeError(`agentId invalid after sanitize: ${raw}`);
return id;
}
createMemoryStore({ agentId: safeAgentId(agent.id) }); Type guard
const isValidAgentId = (id) => typeof id === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(id); Try / catch
try {
const store = createMemoryStore({ agentId });
} catch (e) {
if (e.message === "cave_memory_agent_invalid") {
throw new Error(`agentId '${agentId}' must start with a letter and use only [a-zA-Z0-9_-] (<=128)`, { cause: e });
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Generating agent IDs programmatically from hostnames or emails; using uppercase-free numeric IDs auto-generated by another system; concatenating version suffixes with `/` or `.` separators.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- cave_memory_namespace_invalid
- cave_memory_tenant_invalid
- apiKey, baseURL, and agent are required
- ASGI context resolver or request bounds are invalid
- bedrock endpoint must be runtime or mantle
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/d487802912464e57.
Report an issue: GitHub.
Appendix: 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 3ee70a1026)