JuliusBrussee/caveman · error
caveman agent: duplicate tool name
Error message
caveman agent: duplicate tool name
What it means
agent() freezes the tools array and rejects definitions where two tools share the same name, since tool names must be unique for dispatch. It compares the size of the Set of tool names against the array length.
Solutions
- Deduplicate the tools array (e.g. new Map(tools.map(t => [t.name, t])).values()) before passing it
- Rename one of the conflicting tools to a unique name
- Check how the tools array is assembled for accidental duplicates
- Filter duplicates: tools.filter((t, i, arr) => arr.findIndex(x => x.name === t.name) === i)
Example fix
// before
agent({ id: "bot", tools: [...baseTools, webSearchTool] }); // webSearchTool also in baseTools
// after
const tools = [...new Map([...baseTools, webSearchTool].map(t => [t.name, t])).values()];
agent({ id: "bot", tools }); Defensive patterns
Strategy: validation
Validate before calling
const names = tools.map(t => t.name);
if (new Set(names).size !== names.length) throw new Error("duplicate tool names before agent()"); Type guard
const hasUniqueToolNames = (tools) => new Set(tools.map(t => t.name)).size === tools.length;
Try / catch
try { def = agent(opts); } catch (err) { if (err.message === "caveman agent: duplicate tool name") { opts.tools = dedupeByName(opts.tools); def = agent(opts); } else throw err; } Prevention
- Deduplicate merged tool lists by name before passing to agent()
- Keep a registry keyed by tool name
- Avoid spreading overlapping default/custom tool arrays blindly
When it happens
Trigger: agent({ tools: [searchTool, searchTool] }) — the same tool object passed twice; two distinct tool objects both named 'search'.
Common situations: Spreading a default tool list plus a custom list that both include a common tool; building tools in a loop that reuses a name; concatenating tool sets from multiple modules.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- caveman agent: tool prefix cave_ is reserved by framework
- cave_agent_definition_invalid
- cave_claude_tool_contract_unsupported
- cave_subagent_definition_cycle
- cave_subagent_depth_limit
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/9745727dae33f5e8.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/index.ts:103
];
export function agent(options: {
id: string;
instructions: string | FileSource;
model: Auto | string | Model<Api>;
reasoning?: AgentDefinition["reasoning"];
tools?: ToolDefinition[];
contexts?: ContextDefinition[];
memory?: MemoryDefinition;
output?: OutputDefinition;
sandbox?: AgentDefinition["sandbox"];
}): AgentDefinition {
if (!/^[a-z0-9][a-z0-9_-]{0,95}$/.test(options.id)) {
throw new Error(`caveman agent: invalid agent id ${JSON.stringify(options.id)}`);
}
const tools = Object.freeze([...(options.tools ?? [])]);
if (new Set(tools.map((item) => item.name)).size !== tools.length) {
throw new Error("caveman agent: duplicate tool name");
}
const sandbox = options.sandbox ?? "required";
if (!SANDBOX_MODES.includes(sandbox)) {
throw new Error(`caveman agent: unknown sandbox mode ${JSON.stringify(sandbox)}`);
}
const reserved = tools.find((item) => item.name.startsWith("cave_"));
if (reserved) {
throw new Error(
`caveman agent: tool prefix cave_ is reserved by framework (${reserved.name})`,
);
}
const definition: AgentDefinition = {
kind: "agent",
id: options.id,
instructions: options.instructions,
model: options.model,
reasoning: options.reasoning ?? "low",
tools,View on GitHub (pinned to 3ee70a1026)