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

  1. Deduplicate the tools array (e.g. new Map(tools.map(t => [t.name, t])).values()) before passing it
  2. Rename one of the conflicting tools to a unique name
  3. Check how the tools array is assembled for accidental duplicates
  4. 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

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


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)