{"record":{"id":"3903753d33d38d96","repo":"JuliusBrussee/caveman","slug":"caveman-agent-invalid-agent-id-json-stringify-o","errorCode":null,"errorMessage":"caveman agent: invalid agent id ${JSON.stringify(options.id)}","messagePattern":"caveman agent: invalid agent id (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/index.ts","lineNumber":99,"sourceCode":"const SANDBOX_MODES: readonly AgentDefinition[\"sandbox\"][] = [\n  \"required\",\n  \"fixture\",\n  \"host\",\n];\n\nexport function agent(options: {\n  id: string;\n  instructions: string | FileSource;\n  model: Auto | string | Model<Api>;\n  reasoning?: AgentDefinition[\"reasoning\"];\n  tools?: ToolDefinition[];\n  contexts?: ContextDefinition[];\n  memory?: MemoryDefinition;\n  output?: OutputDefinition;\n  sandbox?: AgentDefinition[\"sandbox\"];\n}): AgentDefinition {\n  if (!/^[a-z0-9][a-z0-9_-]{0,95}$/.test(options.id)) {\n    throw new Error(`caveman agent: invalid agent id ${JSON.stringify(options.id)}`);\n  }\n  const tools = Object.freeze([...(options.tools ?? [])]);\n  if (new Set(tools.map((item) => item.name)).size !== tools.length) {\n    throw new Error(\"caveman agent: duplicate tool name\");\n  }\n  const sandbox = options.sandbox ?? \"required\";\n  if (!SANDBOX_MODES.includes(sandbox)) {\n    throw new Error(`caveman agent: unknown sandbox mode ${JSON.stringify(sandbox)}`);\n  }\n  const reserved = tools.find((item) => item.name.startsWith(\"cave_\"));\n  if (reserved) {\n    throw new Error(\n      `caveman agent: tool prefix cave_ is reserved by framework (${reserved.name})`,\n    );\n  }\n  const definition: AgentDefinition = {\n    kind: \"agent\",\n    id: options.id,","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/index.ts#L81-L117","documentation":"Thrown by the agent() builder (packages/agent/src/index.ts:99): options.id fails the pattern ^[a-z0-9][a-z0-9_-]{0,95}$ — it must start with a lowercase letter or digit, contain only lowercase letters, digits, underscore, and hyphen, and be at most 96 characters. Agent ids appear in locks, ledgers, and tracing, so the format is enforced strictly.","triggerScenarios":"Calling agent({ id, ... }) with an id containing uppercase letters, dots, slashes, spaces, or other symbols; an empty id; an id starting with '-' or '_'; or an id longer than 96 characters.","commonSituations":"Using a human-readable name with spaces or capitals ('Research Agent'); deriving the id from a file path or package name containing dots; trimming/generating ids from user input without sanitization; very long generated ids.","solutions":["Rename the id to lowercase kebab/snake case, e.g. 'research-agent'.","If the id is derived from user input, sanitize it (lowercase, replace invalid chars with '-', enforce length) before calling agent().","Keep ids under 96 characters."],"exampleFix":"// before\nagent({ id: \"Research.Agent/v2\", /* ... */ });\n\n// after\nagent({ id: \"research-agent-v2\", /* ... */ });","handlingStrategy":"validation","validationCode":"const AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,95}$/;\nfunction toAgentId(raw: string): string {\n  const id = raw.toLowerCase().replace(/[^a-z0-9_-]/g, \"-\").replace(/^-+/, \"\").slice(0, 96);\n  if (!AGENT_ID_RE.test(id)) throw new Error(`cannot derive valid agent id from '${raw}'`);\n  return id;\n}","typeGuard":"function isValidAgentId(id: unknown): id is string {\n  return typeof id === \"string\" && /^[a-z0-9][a-z0-9_-]{0,95}$/.test(id);\n}","tryCatchPattern":null,"preventionTips":["Use lowercase kebab-case ids by convention.","Sanitize user-derived ids (lowercase, strip invalid chars, cap length) before agent().","Validate ids at config load time with the same regex the builder uses."],"tags":["validation","naming","agent-id"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}