{"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":null,"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/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/definition-graph.ts#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ntools: [{ kind: \"tool\", name: \"search\", /* a */ }, { kind: \"tool\", name: \"search\", /* b */ }]\n// after\ntools: [{ kind: \"tool\", name: \"search\" }, { kind: \"tool\", name: \"search_web\" }]","handlingStrategy":"validation","validationCode":"function assertUniqueToolNames(tools: ToolDefinition[]): 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":null,"tryCatchPattern":"try {\n  validateAgentGraph(def);\n} catch (e) {\n  if (e instanceof Error && e.message === \"cave_duplicate_tool_name\") {\n    console.error(\"Two tools share a name; deduplicate with a Map keyed by name\");\n  } else throw e;\n}","preventionTips":["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."],"tags":["validation","naming-conflict","tool-definition"],"backgroundTag":"duplicate-identifier","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"}