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
- Create every tool with the library's tool() factory so kind, name, and the implementation-source marker are set correctly
- Filter undefined/null out of dynamically built tool arrays before registration
- 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
- Always build tools with the library's tool() factory so the trust marker is attached
- Filter null/undefined out of conditionally-built tool arrays
- Never clone or serialize tool objects across boundaries; recreate them via the factory
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
- cave_duplicate_tool_name
- cave_untrusted_tool_definition:${declared.name}
- caveman agent: tool Standard JSON Schema converter is invali
- option not found
- cave_harness_adapter_version_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/ca05f7c116a9c47e.
Report an issue: GitHub.