JuliusBrussee/caveman · error
cave_duplicate_tool_name
cave_duplicate_tool_name
Error message
cave_duplicate_tool_name
What it means
Raised while validateAgentGraph visits tool definitions: two tools reachable from one agent share the same name. Tool names are the dispatch key for the model and the runtime, so duplicates would make calls ambiguous; this fires regardless of tool identity.
Solutions
- Deduplicate the tools array (e.g. new Map(tools.map(t => [t.name, t])).values()).
- Rename one of the colliding tools so names are unique within the agent.
- Check helper/composition code that appends tools — guard against adding an already-present name.
Example fix
// before
tools: [{ kind: "tool", name: "search", /* a */ }, { kind: "tool", name: "search", /* b */ }]
// after
tools: [{ kind: "tool", name: "search" }, { kind: "tool", name: "search_web" }] Defensive patterns
Strategy: validation
Validate before calling
function assertUniqueToolNames(tools: ToolDefinition[]): void {
const seen = new Set<string>();
for (const t of tools) {
if (seen.has(t.name)) throw new Error(`duplicate tool name: ${t.name}`);
seen.add(t.name);
}
} Try / catch
try {
validateAgentGraph(def);
} catch (e) {
if (e instanceof Error && e.message === "cave_duplicate_tool_name") {
console.error("Two tools share a name; deduplicate with a Map keyed by name");
} else throw e;
} Prevention
- Deduplicate tools arrays with new Map(tools.map(t => [t.name, t])).values() when merging sources.
- Give each tool a unique, descriptive name at creation time.
- Guard any code that appends tools against re-adding an existing name.
When it happens
Trigger: Adding the same tool object twice to tools, or two distinct tools accidentally given identical name strings; visit tracks names in a Set and throws on the second occurrence.
Common situations: Merging tool lists from multiple sources without deduplication, copy-pasting a tool entry with only its implementation changed, generated tool sets where a naming template collides, or appending a tool that was already registered by a helper.
Related errors
- cave_reserved_tool_name
- cave_tool_definition_invalid
- caveman agent: invalid tool name
- caveman agent: unknown tool effect
- caveman agent: unknown tool result policy
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/030eca918d76be5d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/definition-graph.ts:40
if (depth > 8) throw new Error("cave_subagent_depth_limit");
if (active.has(definition)) throw new Error("cave_subagent_definition_cycle");
// Host mode is an opt-in the root makes for itself. A descendant cannot use
// it to run closures outside an ancestor's required containment.
if (sandboxRequired && definition.sandbox === "host") {
throw new Error("cave_host_sandbox_nested_under_required");
}
const memo = visited[sandboxRequired ? 1 : 0]!;
if (memo.has(definition)) return;
active.add(definition);
const childSandboxRequired = sandboxRequired ||
definition.sandbox === "required";
const names = new Set<string>();
for (const declared of definition.tools) {
if (!declared || declared.kind !== "tool" ||
typeof declared.name !== "string") {
throw new Error("cave_tool_definition_invalid");
}
if (names.has(declared.name)) throw new Error("cave_duplicate_tool_name");
names.add(declared.name);
if (declared.name.startsWith("cave_")) {
throw new Error(`cave_reserved_tool_name:${declared.name}`);
}
if (typeof Reflect.get(declared, TOOL_IMPLEMENTATION_SOURCE) !== "string") {
throw new Error(`cave_untrusted_tool_definition:${declared.name}`);
}
if (declared.runtime?.kind !== "subagent") continue;
const child = declared.runtime.definition as AgentDefinition;
visit(child, depth + 1, childSandboxRequired);
}
active.delete(definition);
memo.add(definition);
};
visit(root, 0, false);
}
View on GitHub (pinned to 3ee70a1026)