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
- Check that parent runtime and worker come from the same package version; reinstall/align @caveman-ai/agent so the request schema matches.
- Fix the tool name: use only [A-Za-z0-9_-], starting with a letter, at most 128 characters, and not framework-reserved `cave_`.
- Ensure toolDefinitionSha256 is produced by toolDefinitionSHA256 (64 lowercase hex) rather than hand-supplied.
- 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
- Keep parent runtime and worker on the exact same package version to avoid schema skew.
- Name tools with letters, digits, `_`, `-` only, starting with a letter, max 128 chars.
- Always derive toolDefinitionSha256 from toolDefinitionSHA256, never hand-write digests.
- Pass allowSideEffects/allowNetwork as explicit booleans when constructing spawn requests.
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
- canonical span
- cave_agent_definition_invalid
- cave_context_ir_invalid
- cave_output_schema_mismatch
- cave_output_schema_mismatch
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)