JuliusBrussee/caveman · error
cave_claude_tool_schema_unsupported
cave_claude_tool_schema_unsupported
Error message
cave_claude_tool_schema_unsupported:${item.name} What it means
Each tool's JSON-Schema input must be convertible by z.fromJSONSchema into a z.ZodObject. Schemas that convert to another Zod type (e.g. a bare array, string, or anyOf at the top level) are rejected, with the tool name appended to the message.
Solutions
- Wrap the schema in an object type: {"type":"object","properties":{...},"required":[...] }
- Move list inputs under a named property (e.g. {"items": {...}} becomes {"type":"object","properties":{"items":...}})
- Simplify the root schema so z.fromJSONSchema yields a ZodObject
Example fix
// before
input: { type: "array", items: { type: "string" } }
// after
input: { type: "object", properties: { values: { type: "array", items: { type: "string" } } }, required: ["values"] } Defensive patterns
Strategy: validation
Validate before calling
for (const t of definition.tools) {
const conv = z.fromJSONSchema(t.input);
if (!(conv instanceof z.ZodObject)) throw new Error(`tool ${t.name} input schema must be a top-level object`);
} Type guard
const isObjectSchema = (t) => t.input?.type === "object";
Try / catch
try { await runClaudeAgent(opts); } catch (e) { if (String(e.message).startsWith("cave_claude_tool_schema_unsupported:")) { /* fix that tool's schema and retry */ } throw e; } Prevention
- Always make tool input schemas top-level object types
- Test-convert each schema with z.fromJSONSchema in unit tests
- Avoid root-level anyOf/oneOf in tool schemas
When it happens
Trigger: Declaring a tool whose input JSON schema's top level is not an object — e.g. {"type":"array"}, {"type":"string"}, or an anyOf/oneOf at the root.
Common situations: Hand-writing tool schemas copied from OpenAI-style function definitions where parameters wrap differently; generating schemas from a serializer that emits non-object roots.
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
- cave_claude_tool_contract_unsupported
- cave_claude_tool_result_invalid
- AutoGen tools and workbench are mutually exclusive
- canonical span
- cave_agent_definition_invalid
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e90b1dc8b592853c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/claude-runtime.ts:151
// lane can only ever be an unlocked run: a missing gateway degrades to
// observe-only rather than blocking a first response.
const { useGateway } = await resolveCaveRoute(gatewayURL, options, false);
const instructions = assembleSystemPrompt(definition, lowered);
const runID = crypto.randomUUID();
const sessionID = `claude-${definition.id}-${runID}`;
const prefixSHA256 = sha256(stableStringify({
instructions,
tools: definition.tools.map((item) => ({
name: item.name,
description: item.description,
input: item.input,
})),
}));
const toolNames = new Set<string>();
const mcpTools = definition.tools.map((item) => {
const converted = z.fromJSONSchema(item.input as Record<string, unknown>);
if (!(converted instanceof z.ZodObject)) {
throw new Error(`cave_claude_tool_schema_unsupported:${item.name}`);
}
const execute = createHarnessToolExecutor({
definition,
tool: item,
sandbox,
...(options.sandboxProfile === undefined ? {} : { sandboxProfile: options.sandboxProfile }),
...(options.engineBin === undefined ? {} : { engineBin: options.engineBin }),
});
const wireName = `mcp__caveman_agent__${item.name}`;
toolNames.add(wireName);
return claudeTool(
item.name,
item.description,
converted.shape,
async (args) => {
const value = await execute(args, controller.signal);
if (!isRecord(value) || !Array.isArray(value.content)) {
throw new Error(`cave_claude_tool_result_invalid:${item.name}`);View on GitHub (pinned to 3ee70a1026)