{"record":{"id":"ca05f7c116a9c47e","repo":"JuliusBrussee/caveman","slug":"cave-tool-definition-invalid","errorCode":"cave_tool_definition_invalid","errorMessage":"cave_tool_definition_invalid","messagePattern":"cave_tool_definition_invalid","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/definition-graph.ts","lineNumber":38,"sourceCode":"      throw new Error(\"cave_agent_definition_invalid\");\n    }\n    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);","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/definition-graph.ts#L20-L56","documentation":"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).","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before\nconst tools = [maybeTool, { kind: \"tool\", name: \"fake\" }];\n\n// after\nconst tools = [maybeTool].filter(Boolean);\nconst real = tool({ name: \"fake\", description: \"...\", input: schema.object({}), execute: async () => \"ok\" });","handlingStrategy":"type-guard","validationCode":"function assertToolsWellFormed(tools: unknown[]): void {\n  for (const t of tools) {\n    if (!t || typeof t !== \"object\" || (t as any).kind !== \"tool\" || typeof (t as any).name !== \"string\") {\n      throw new Error(`invalid tool entry in definition: ${String(t)}`);\n    }\n  }\n}","typeGuard":"function isToolDefinition(t: unknown): t is { kind: \"tool\"; name: string } {\n  return typeof t === \"object\" && t !== null &&\n    (t as { kind?: unknown }).kind === \"tool\" &&\n    typeof (t as { name?: unknown }).name === \"string\";\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["validation","tool-definition","factory","trust"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}