{"record":{"id":"647d67daabb20a44","repo":"JuliusBrussee/caveman","slug":"cave-untrusted-tool-definition-declared-name","errorCode":null,"errorMessage":"cave_untrusted_tool_definition:${declared.name}","messagePattern":"cave_untrusted_tool_definition:(.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/definition-graph.ts","lineNumber":46,"sourceCode":"    }\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\n/**\n * True when any agent in the graph opts into host mode.\n *\n * Lock eligibility is a property of the whole graph, not of its root: a host\n * subagent runs its tool closures in the host process just as a host root does,\n * so its evidence shows no containment either. Follows the same subagent edges","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/definition-graph.ts#L28-L64","documentation":"Thrown by validateAgentGraph (packages/agent/src/definition-graph.ts:46): a tool definition lacks the TOOLS_IMPLEMENTATION_SOURCE marker (the Symbol.for('@caveman-ai/agent:tool-implementation-source') string property). Only tools created by the framework's own tool()/subagent() factories carry that symbol, so its absence means the object was hand-forged rather than produced by the trusted builder API.","triggerScenarios":"Passing a plain object literal (or an object spread/cloned from a real tool) in AgentDefinition.tools and then running validateAgentGraph, compile, or the dev/build CLI. Structured cloning, JSON round-trips, or {...realTool} spreads drop non-enumerable/symbol properties and also trigger it.","commonSituations":"Hand-assembling a ToolDefinition to bypass the factory; serializing/deserializing tool definitions; deep-cloning a tool with a spread or a cloner that drops symbol-keyed properties; constructing tools with a different (incompatible) version of the package than the one validating the graph.","solutions":["Create every tool with the exported tool() (and subagents with subagent()) from @caveman-ai/agent instead of object literals.","If definitions must cross a process/serialization boundary, re-create tools via the factory on the receiving side rather than deserializing tool objects.","Check for duplicate installations of @caveman-ai/agent (Symbol.for is cross-realm, but a factory that stops setting the symbol indicates a version mismatch) and align versions."],"exampleFix":"// before\nconst myTool = { kind: \"tool\", name: \"search\", /* ... */ } as ToolDefinition;\n\n// after\nimport { tool } from \"@caveman-ai/agent\";\nconst myTool = tool({ name: \"search\", /* ... */ });","handlingStrategy":"type-guard","validationCode":"import { tool } from \"@caveman-ai/agent\";\n// Every tool in the array must come from the factory — verify by construction:\nfunction assertFactoryTools(tools: unknown[]): void {\n  for (const t of tools) {\n    if (typeof Reflect.get(Object(t), Symbol.for(\"@caveman-ai/agent:tool-implementation-source\")) !== \"string\") {\n      throw new Error(`tool '${(t as { name?: string }).name}' was not created via tool()`);\n    }\n  }\n}","typeGuard":"const TOOL_SRC = Symbol.for(\"@caveman-ai/agent:tool-implementation-source\");\nfunction isFactoryTool(value: unknown): value is ToolDefinition {\n  return typeof value === \"object\" && value !== null &&\n    typeof Reflect.get(value, TOOL_SRC) === \"string\";\n}","tryCatchPattern":null,"preventionTips":["Never hand-write ToolDefinition object literals; always use tool().","Never clone tools with spread/JSON — re-create them through the factory.","Keep a single version of @caveman-ai/agent installed across the workspace."],"tags":["validation","security","factory","symbols","agent-graph"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}