{"record":{"id":"030eca918d76be5d","repo":"JuliusBrussee/caveman","slug":"cave-duplicate-tool-name","errorCode":"cave_duplicate_tool_name","errorMessage":"cave_duplicate_tool_name","messagePattern":"cave_duplicate_tool_name","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/definition-graph.ts","lineNumber":40,"sourceCode":"    if (depth > 8) throw new Error(\"cave_subagent_depth_limit\");\n    if (active.has(definition)) throw new Error(\"cave_subagent_definition_cycle\");\n    // Host mode is an opt-in the root makes for itself. A descendant cannot use\n    // it to run closures outside an ancestor's required containment.\n    if (sandboxRequired && definition.sandbox === \"host\") {\n      throw new Error(\"cave_host_sandbox_nested_under_required\");\n    }\n    const memo = visited[sandboxRequired ? 1 : 0]!;\n    if (memo.has(definition)) return;\n    active.add(definition);\n    const childSandboxRequired = sandboxRequired ||\n      definition.sandbox === \"required\";\n    const names = new Set<string>();\n    for (const declared of definition.tools) {\n      if (!declared || declared.kind !== \"tool\" ||\n          typeof declared.name !== \"string\") {\n        throw new Error(\"cave_tool_definition_invalid\");\n      }\n      if (names.has(declared.name)) throw new Error(\"cave_duplicate_tool_name\");\n      names.add(declared.name);\n      if (declared.name.startsWith(\"cave_\")) {\n        throw new Error(`cave_reserved_tool_name:${declared.name}`);\n      }\n      if (typeof Reflect.get(declared, TOOL_IMPLEMENTATION_SOURCE) !== \"string\") {\n        throw new Error(`cave_untrusted_tool_definition:${declared.name}`);\n      }\n      if (declared.runtime?.kind !== \"subagent\") continue;\n      const child = declared.runtime.definition as AgentDefinition;\n      visit(child, depth + 1, childSandboxRequired);\n    }\n    active.delete(definition);\n    memo.add(definition);\n  };\n\n  visit(root, 0, false);\n}\n","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/definition-graph.ts#L22-L58","documentation":"Within a single AgentDefinition, tool names must be unique — a Set tracks names as the tools array is walked and any repeat throws cave_duplicate_tool_name before the graph is used. Distinct agents may share tool names; the constraint is per-definition.","triggerScenarios":"Registering the same tool instance twice in one agent's tools array, or two different tools with the same name (e.g. two 'search' tools from different modules) in one definition.","commonSituations":"Combining toolkits that each export a 'read' or 'bash' tool; spread-and-override assembly where the override was appended instead of replacing; default tools merged with custom ones of the same name.","solutions":["De-duplicate by name before registration (see validation snippet in the defense section)","Namespace colliding tools: rename one to 'fs_read' vs 'web_read' via its factory call","When merging toolkits, build a Map keyed by name with explicit conflict resolution"],"exampleFix":"// before\nconst tools = [...fsToolkit, ...webToolkit]; // both contain name: \"read\"\n\n// after\nconst byName = new Map();\nfor (const t of [...fsToolkit, ...webToolkit]) byName.set(t.name === \"read\" && byName.has(\"read\") ? \"web_read\" : t.name, t);\nconst tools = [...byName.values()];","handlingStrategy":"validation","validationCode":"function assertUniqueToolNames(tools: Array<{ name: string }>): void {\n  const seen = new Set<string>();\n  for (const t of tools) {\n    if (seen.has(t.name)) throw new Error(`duplicate tool name: ${t.name}`);\n    seen.add(t.name);\n  }\n}","typeGuard":"function hasDuplicateToolNames(tools: Array<{ name: string }>): boolean {\n  return new Set(tools.map((t) => t.name)).size !== tools.length;\n}","tryCatchPattern":null,"preventionTips":["Merge toolkits through a name-keyed Map with explicit conflict resolution","Namespace tools by domain (fs_read, web_read) when combining libraries","Run a duplicate-name check in CI for programmatically assembled agent definitions"],"tags":["validation","tool-definition","naming","conflict"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}