JuliusBrussee/caveman · error

cave_sandbox_unknown_tool

cave_sandbox_unknown_tool

Error message

cave_sandbox_unknown_tool

What it means

After descending agentPath, the worker looks up request.tool in the final definition's tools. It requires exactly one match whose runtime.kind is NOT "subagent" (you cannot invoke a subagent slot directly as a tool). Zero matches, multiple matches, or a match that is itself a subagent all produce this error.

Source

Thrown at packages/agent/src/tool-worker.ts:127

    throw new Error("cave_sandbox_definition_mismatch");
  }
  const visited = new Set<AgentDefinition>([definition]);
  for (const name of request.agentPath) {
    const delegated = definition.tools.filter((item) =>
      item.name === name && item.runtime?.kind === "subagent"
    );
    if (delegated.length !== 1) throw new Error("cave_sandbox_unknown_subagent");
    const child = delegated[0]!.runtime!.definition as AgentDefinition;
    if (!child || child.kind !== "agent") {
      throw new Error("cave_sandbox_subagent_definition_invalid");
    }
    if (visited.has(child)) throw new Error("cave_sandbox_subagent_cycle");
    visited.add(child);
    definition = child;
  }
  const selectedTools = definition.tools.filter((item) => item.name === request.tool);
  if (selectedTools.length !== 1 || selectedTools[0]!.runtime?.kind === "subagent") {
    throw new Error("cave_sandbox_unknown_tool");
  }
  const selected = selectedTools[0]!;
  if (toolDefinitionSHA256(selected) !== request.toolDefinitionSha256) {
    throw new Error("cave_sandbox_tool_definition_mismatch");
  }
  if (selected.effect !== "read" && request.allowSideEffects !== true) {
    throw new Error("cave_sandbox_side_effect_denied");
  }
  const value = await selected.execute(request.params as never, AbortSignal.timeout(selected.timeoutMs));
  writeResult({ ok: true, value });
} catch (error) {
  writeResult({ ok: false, code: failureCode(error) });
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Confirm request.tool matches exactly one non-subagent tool name in the agent that agentPath resolves to.
  2. Fix agentPath so it terminates at the agent that actually owns the tool.
  3. To run a subagent's tool, extend agentPath with the subagent name and target its inner tool; never point request.tool at a subagent slot.

Example fix

// before: tool "search" lives inside subagent "research"
{ agentPath: [], tool: "search" } // cave_sandbox_unknown_tool

// after
{ agentPath: ["research"], tool: "search" }
Defensive patterns

Strategy: validation

Validate before calling

function toolResolves(def: AgentDefinition, toolName: string): boolean {
  const matches = def.tools.filter((t) => t.name === toolName);
  return matches.length === 1 && matches[0].runtime?.kind !== "subagent";
}

Type guard

function isExecutableTool(t: { runtime?: { kind?: string } }): boolean {
  return t.runtime?.kind !== "subagent";
}

Prevention

When it happens

Trigger: request.tool names a nonexistent tool in the resolved agent; the name resolves only to a subagent-runtime entry; two tools share the name; the tool lives on a different agent in the path than agentPath reached.

Common situations: Tool renamed or removed while a queued worker request still names the old identifier; agentPath too short/long so resolution lands on the wrong agent; invoking a subagent by its tool name instead of going through normal delegation.

Related errors


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