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 request envelope it reads from stdin before doing anything: entry must be a string, toolDefinitionSha256 must be a 64-char lowercase hex string, tool must match /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/, and allowSideEffects/allowNetwork must be booleans. Any violation throws `cave_sandbox_request_invalid`. This is fail-closed protocol validation between the parent runtime and the sandboxed worker.

Solutions

  1. Check that parent runtime and worker come from the same package version; reinstall/align @caveman-ai/agent so the request schema matches.
  2. Fix the tool name: use only [A-Za-z0-9_-], starting with a letter, at most 128 characters, and not framework-reserved `cave_`.
  3. Ensure toolDefinitionSha256 is produced by toolDefinitionSHA256 (64 lowercase hex) rather than hand-supplied.
  4. If you spawn the worker yourself, pass allowSideEffects and allowNetwork as real booleans (never undefined/strings).

Example fix

// before
spawnWorker({ tool: "my.tool", toolDefinitionSha256: sha, allowSideEffects: "yes", allowNetwork: 0 });

// after
spawnWorker({ tool: "my_tool", toolDefinitionSha256: sha256Hex, allowSideEffects: true, allowNetwork: false });
Defensive patterns

Strategy: validation

Validate before calling

const TOOL_NAME = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
const SHA256 = /^[a-f0-9]{64}$/;
function isValidWorkerRequest(r: unknown): boolean {
  const q = r as Record<string, unknown>;
  return typeof q.entry === "string" &&
    typeof q.toolDefinitionSha256 === "string" && SHA256.test(q.toolDefinitionSha256) &&
    typeof q.tool === "string" && TOOL_NAME.test(q.tool) &&
    typeof q.allowSideEffects === "boolean" &&
    typeof q.allowNetwork === "boolean";
}

Type guard

const isWorkerRequest = (r: unknown): r is ToolWorkerRequest =>
  typeof r === "object" && r !== null &&
  typeof (r as any).entry === "string" &&
  typeof (r as any).toolDefinitionSha256 === "string" && /^[a-f0-9]{64}$/.test((r as any).toolDefinitionSha256) &&
  typeof (r as any).tool === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test((r as any).tool) &&
  typeof (r as any).allowSideEffects === "boolean" &&
  typeof (r as any).allowNetwork === "boolean";

Try / catch

try {
  const result = await spawnToolWorker(request);
} catch (err) {
  if (err instanceof Error && err.message.includes("cave_sandbox_request_invalid")) {
    console.error("worker request failed schema validation — check tool name, sha256, and boolean flags");
  }
  throw err;
}

Prevention

When it happens

Trigger: The parent runtime sends a malformed spawn request: missing field, undefined allowSideEffects/allowNetwork, a tool name that is empty, starts with a digit, contains invalid characters, or exceeds 128 chars, or a non-hex/wrong-length tool digest.

Common situations: Version skew between parent runtime and worker code (one changed the request schema); hand-crafted or replayed worker requests in custom harnesses; a tool name from dynamic data that violates the identifier pattern (e.g. contains a dot or starts with `_`); JSON coercion turning a boolean field into a string.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/33ae5f3b1f108c5a. Report an issue: GitHub.

Appendix: 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 3ee70a1026)