JuliusBrussee/caveman · error · Error

cave_tool_definition_invalid

cave_tool_definition_invalid

Error message

cave_tool_definition_invalid

What it means

Each entry in an agent's tools array must be an object with kind === 'tool' and a string name. Additionally, the implementation must carry the TOOL_IMPLEMENTATION_SOURCE marker (checked via Reflect.get) proving it was created by the library's own tool() factory — prototypes or hand-forged tool objects are treated as untrusted and rejected (the separate cave_untrusted_tool_definition error carries the name).

Source

Thrown at packages/agent/src/definition-graph.ts:38

      throw new Error("cave_agent_definition_invalid");
    }
    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 27d5a3981a)

Solutions

  1. Create every tool with the library's tool() factory so kind, name, and the implementation-source marker are set correctly
  2. Filter undefined/null out of dynamically built tool arrays before registration
  3. Do not clone or serialize tool objects — re-create them from their definitions at the target boundary

Example fix

// before
const tools = [maybeTool, { kind: "tool", name: "fake" }];

// after
const tools = [maybeTool].filter(Boolean);
const real = tool({ name: "fake", description: "...", input: schema.object({}), execute: async () => "ok" });
Defensive patterns

Strategy: type-guard

Validate before calling

function assertToolsWellFormed(tools: unknown[]): void {
  for (const t of tools) {
    if (!t || typeof t !== "object" || (t as any).kind !== "tool" || typeof (t as any).name !== "string") {
      throw new Error(`invalid tool entry in definition: ${String(t)}`);
    }
  }
}

Type guard

function isToolDefinition(t: unknown): t is { kind: "tool"; name: string } {
  return typeof t === "object" && t !== null &&
    (t as { kind?: unknown }).kind === "tool" &&
    typeof (t as { name?: unknown }).name === "string";
}

Prevention

When it happens

Trigger: Passing null/undefined in the tools array, an AgentDefinition where a tool was expected, an object with kind 'agent', a missing or non-string name, or a plain object literal mimicking a tool without having gone through tool().

Common situations: Arrays built with conditional spreads producing undefined entries; spreading config objects that replaced a tool with a spec object; attempting to construct tools via Object.assign or structuredClone, which drops the symbol-keyed implementation marker.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/ca05f7c116a9c47e. Report an issue: GitHub.