{"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":null,"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/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/index.ts#L81-L117","documentation":"The agent() definition factory validates the agent id against /^[a-z0-9][a-z0-9_-]{0,95}$/ and throws with a JSON-stringified copy of the invalid id. Ids must start with a lowercase letter or digit, contain only lowercase alphanumerics, underscores, and hyphens, and be at most 96 characters.","triggerScenarios":"agent({ id: 'My Agent' }) (uppercase/space); empty string; id starting with '-' or '_'; ids longer than 96 chars; ids with dots or unicode characters.","commonSituations":"Deriving ids from human-readable project names with spaces/camelCase; using UUIDs with uppercase; templating ids from environment names like 'Prod.EU'.","solutions":["Slugify the id: lowercase, replace invalid characters with '-' or '_'","Strip leading characters that are not [a-z0-9] and truncate to 96 chars","Validate the id with the same regex in your own code before calling agent()","Use a stable kebab-case identifier instead of a display name"],"exampleFix":"// before\nagent({ id: \"My Research Agent\", ... });\n// after\nconst id = \"My Research Agent\".toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+/, \"\").slice(0, 96);\nagent({ id, ... });","handlingStrategy":"validation","validationCode":"const AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,95}$/;\nif (!AGENT_ID_RE.test(id)) throw new Error(`invalid agent id: ${id}`);","typeGuard":"const isValidAgentId = (id) => typeof id === \"string\" && /^[a-z0-9][a-z0-9_-]{0,95}$/.test(id);","tryCatchPattern":"try { def = agent({ id, ...rest }); } catch (err) { if (String(err.message).startsWith(\"caveman agent: invalid agent id\")) { def = agent({ id: slugify(id), ...rest }); } else throw err; }","preventionTips":["Slugify display names into ids at the boundary","Never derive ids from user-visible labels directly","Keep ids kebab-case and <= 96 chars by convention"],"tags":["validation","agent-definition","identifier"],"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"}