JuliusBrussee/caveman · error
caveman agent: invalid tool name
Error message
caveman agent: invalid tool name ${JSON.stringify(options.name)} What it means
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.
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
Example fix
// before
tool({ name: `${repoId}.search`, ... }) // dot invalid, may start with digit
// after
const name = `tool_${repoId}_search`.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/^[^a-zA-Z]/, "t").slice(0, 128);
tool({ name, ... }) Defensive patterns
Strategy: validation
Validate before calling
const TOOL_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
function validToolName(name: unknown): name is string {
return typeof name === "string" && TOOL_NAME_RE.test(name);
} Type guard
function isToolName(v: unknown): v is `${string}` {
return typeof v === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(v);
} Try / catch
try {
const def = tool(options);
} catch (err) {
if (err instanceof Error && err.message.includes("invalid tool name")) {
// sanitize options.name and retry once
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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_duplicate_tool_name
- cave_tool_definition_invalid
- caveman agent: unknown tool effect
- caveman agent: unknown tool result policy
- compat upstream name
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/27d492768c5bfa95.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/primitives.ts:129
inputJSONSchema?: never;
}
export function tool<TInput extends TSchema, TResult>(
options: ToolOptions<TInput, TResult>,
): ToolDefinition<Static<TInput>, TResult>;
export function tool<Input, Output, TResult>(
options: StandardToolOptions<Input, Output, TResult>,
): ToolDefinition<Output, TResult>;
export function tool<Input, Output, TResult>(
options: StandardJSONToolOptions<Input, Output, TResult>,
): ToolDefinition<Output, TResult>;
export function tool(
options: ToolOptions<TSchema, unknown> |
StandardToolOptions<unknown, unknown, unknown> |
StandardJSONToolOptions<unknown, unknown, unknown>,
): ToolDefinition<unknown, unknown> {
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(options.name)) {
throw new Error(`caveman agent: invalid tool name ${JSON.stringify(options.name)}`);
}
if (!["read", "write", "idempotent", "external"].includes(options.effect)) {
throw new Error(`caveman agent: unknown tool effect ${JSON.stringify(options.effect)}`);
}
const result = typeof options.result === "object"
? artifactResultPolicy(options.result)
: options.result ?? "auto";
if (!["auto", "inline", "page", "compress", "exact_ccr"].includes(result)) {
throw new Error(`caveman agent: unknown tool result policy ${JSON.stringify(result)}`);
}
const timeoutMs = options.timeoutMs ?? 30_000;
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
throw new Error("caveman agent: tool timeoutMs must be a positive integer");
}
const standard = standardToolSchema(options.input);
let input: TSchema;
if (standard === undefined) {
input = options.input as TSchema;View on GitHub (pinned to 3ee70a1026)