JuliusBrussee/caveman · error

cave_sandbox_request_invalid

cave_sandbox_request_invalid

Error message

cave_sandbox_request_invalid

What it means

The tool worker validates the stdin request frame against a strict shape before doing anything: `entry` must be a string; `agentPath` an array of at most 8 strings matching /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/; both digests 64-char lowercase hex; `tool` a name-shaped string; and `allowSideEffects`/`allowNetwork` booleans. Any deviation throws cave_sandbox_request_invalid — the protocol fails closed on malformed input because the worker is a trust boundary.

Source

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

  }
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
}

try {
  const request = await readRequest();
  if (typeof request.entry !== "string" || !Array.isArray(request.agentPath) ||
      request.agentPath.length > 8 ||
      request.agentPath.some((item) => typeof item !== "string" ||
        !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(item)) ||
      typeof request.rootDefinitionSha256 !== "string" ||
      !/^[a-f0-9]{64}$/.test(request.rootDefinitionSha256) ||
      typeof request.toolDefinitionSha256 !== "string" ||
      !/^[a-f0-9]{64}$/.test(request.toolDefinitionSha256) ||
      typeof request.tool !== "string" ||
      !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(request.tool) ||
      typeof request.allowSideEffects !== "boolean" ||
      typeof request.allowNetwork !== "boolean") {
    throw new Error("cave_sandbox_request_invalid");
  }
  if (request.allowNetwork !== true) installNetworkDeny();
  const imported = await import(request.entry) as { default?: AgentDefinition; agent?: AgentDefinition };
  let definition = imported.default ?? imported.agent;
  if (!definition || definition.kind !== "agent") throw new Error("cave_sandbox_agent_export_missing");
  validateAgentGraph(definition);
  if (agentDefinitionSHA256(definition) !== request.rootDefinitionSha256) {
    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");

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Reinstall/rebuild so the runtime and tool worker come from the same package version.
  2. If invoking the worker manually, replicate the documented frame exactly (string entry, ≤8-segment agentPath, 64-hex digests, name-shaped tool, boolean flags).
  3. Reduce subagent nesting depth so agentPath stays within 8 segments.

Example fix

// before: manual test frame
{ entry: 42, agentPath: ["a","b","c","d","e","f","g","h","i"], ... }

// after
{ entry: "./dist/agent.js", agentPath: ["a","b"], rootDefinitionSha256: "<64 hex>", toolDefinitionSha256: "<64 hex>", tool: "myTool", params: {}, allowSideEffects: false, allowNetwork: false }
Defensive patterns

Strategy: type-guard

Validate before calling

function isHex64(v: unknown): v is string { return typeof v === "string" && /^[a-f0-9]{64}$/.test(v); }
const NAME = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;

Type guard

type SandboxRequest = { entry: string; agentPath: string[]; rootDefinitionSha256: string; toolDefinitionSha256: string; tool: string; params: unknown; allowSideEffects: boolean; allowNetwork: boolean };
function isSandboxRequest(r: unknown): r is SandboxRequest {
  if (typeof r !== "object" || r === null) return false;
  const v = r as Record<string, unknown>;
  return typeof v.entry === "string" &&
    Array.isArray(v.agentPath) && v.agentPath.length <= 8 &&
    v.agentPath.every((s) => typeof s === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(s)) &&
    typeof v.rootDefinitionSha256 === "string" && /^[a-f0-9]{64}$/.test(v.rootDefinitionSha256) &&
    typeof v.toolDefinitionSha256 === "string" && /^[a-f0-9]{64}$/.test(v.toolDefinitionSha256) &&
    typeof v.tool === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(v.tool) &&
    typeof v.allowSideEffects === "boolean" && typeof v.allowNetwork === "boolean";
}

Try / catch

try {
  await runWorker(frame);
} catch (error) {
  if (error instanceof Error && error.message === "cave_sandbox_request_invalid") {
    // frame rejected at the trust boundary: check schema and version parity, never resend unchanged
  } else throw error;
}

Prevention

When it happens

Trigger: A parent runtime and worker built from mismatched versions exchanging different frame schemas; a hand-rolled or corrupted stdin write to the worker; agentPath longer than 8 levels or containing invalid characters.

Common situations: Version drift after a partial upgrade of @caveman-ai/agent (stale compiled worker, mixed node_modules); invoking the worker binary directly for testing with an ad-hoc JSON payload; deeply nested subagent paths exceeding the depth-8 path cap.

Related errors


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