{"record":{"id":"27d492768c5bfa95","repo":"JuliusBrussee/caveman","slug":"caveman-agent-invalid-tool-name-json-stringify","errorCode":null,"errorMessage":"caveman agent: invalid tool name ${JSON.stringify(options.name)}","messagePattern":"caveman agent: invalid tool name (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":129,"sourceCode":"  inputJSONSchema?: never;\n}\n\nexport function tool<TInput extends TSchema, TResult>(\n  options: ToolOptions<TInput, TResult>,\n): ToolDefinition<Static<TInput>, TResult>;\nexport function tool<Input, Output, TResult>(\n  options: StandardToolOptions<Input, Output, TResult>,\n): ToolDefinition<Output, TResult>;\nexport function tool<Input, Output, TResult>(\n  options: StandardJSONToolOptions<Input, Output, TResult>,\n): ToolDefinition<Output, TResult>;\nexport function tool(\n  options: ToolOptions<TSchema, unknown> |\n    StandardToolOptions<unknown, unknown, unknown> |\n    StandardJSONToolOptions<unknown, unknown, unknown>,\n): ToolDefinition<unknown, unknown> {\n  if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(options.name)) {\n    throw new Error(`caveman agent: invalid tool name ${JSON.stringify(options.name)}`);\n  }\n  if (![\"read\", \"write\", \"idempotent\", \"external\"].includes(options.effect)) {\n    throw new Error(`caveman agent: unknown tool effect ${JSON.stringify(options.effect)}`);\n  }\n  const result = typeof options.result === \"object\"\n    ? artifactResultPolicy(options.result)\n    : options.result ?? \"auto\";\n  if (![\"auto\", \"inline\", \"page\", \"compress\", \"exact_ccr\"].includes(result)) {\n    throw new Error(`caveman agent: unknown tool result policy ${JSON.stringify(result)}`);\n  }\n  const timeoutMs = options.timeoutMs ?? 30_000;\n  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {\n    throw new Error(\"caveman agent: tool timeoutMs must be a positive integer\");\n  }\n  const standard = standardToolSchema(options.input);\n  let input: TSchema;\n  if (standard === undefined) {\n    input = options.input as TSchema;","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/primitives.ts#L111-L147","documentation":"tool() validates the tool name against /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/ before building the ToolDefinition. A name must start with a letter, contain only letters, digits, underscores and hyphens, and be at most 128 chars. Anything else throws this error so invalid identifiers never reach the model/provider layer.","triggerScenarios":"tool({ name: \"1st_tool\" }) (starts with digit), tool({ name: \"my tool\" }) (space), tool({ name: \"read.file\" }) (dot), names > 128 chars, or an undefined/non-string name.","commonSituations":"Generating tool names dynamically (e.g. prefixing with a numeric id or including a namespace dot); copying a display name with spaces; names built by slugify functions that leave dots or slashes; exceeding the 128-char cap with long generated names.","solutions":["Rename the tool to match ^[a-zA-Z][a-zA-Z0-9_-]{0,127}$ (letter first, then letters/digits/_/-)","Sanitize dynamic names: strip invalid chars, replace separators with underscores, and ensure the first character is a letter","Truncate or shorten generated names to 128 characters or fewer"],"exampleFix":"// before\ntool({ name: `${repoId}.search`, ... }) // dot invalid, may start with digit\n// after\nconst name = `tool_${repoId}_search`.replace(/[^a-zA-Z0-9_-]/g, \"_\").replace(/^[^a-zA-Z]/, \"t\").slice(0, 128);\ntool({ name, ... })","handlingStrategy":"validation","validationCode":"const TOOL_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;\nfunction validToolName(name: unknown): name is string {\n  return typeof name === \"string\" && TOOL_NAME_RE.test(name);\n}","typeGuard":"function isToolName(v: unknown): v is `${string}` {\n  return typeof v === \"string\" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(v);\n}","tryCatchPattern":"try {\n  const def = tool(options);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"invalid tool name\")) {\n    // sanitize options.name and retry once\n  } else throw err;\n}","preventionTips":["Sanitize generated names: letters/digits/_/- only, must start with a letter, max 128 chars","Share a single slugify helper for dynamic tool naming","Type the name field as a validated branded string to fail at compile time where possible"],"tags":["validation","naming","tool-definition"],"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"}