JuliusBrussee/caveman · error
cave_claude_tool_result_invalid
cave_claude_tool_result_invalid
Error message
cave_claude_tool_result_invalid:${item.name} What it means
Tool executors in the Claude bridge must return a record with a content array (an array of { type: "text", text: string } blocks). If a tool's execute returns null, a non-record, or a record without an array content field, this error naming the tool is thrown.
Solutions
- Return { content: [{ type: "text", text: "..." }] } from the tool's execute
- Wrap existing return values at the end of the executor: return { content: [{ type: "text", text: JSON.stringify(value) }] }
- Add a runtime check/log at the tool boundary during development to catch malformed results early
Example fix
// before
return "done processing file";
// after
return { content: [{ type: "text", text: "done processing file" }] }; Defensive patterns
Strategy: type-guard
Validate before calling
const result = await toolExecute(args);
if (!isRecord(result) || !Array.isArray(result.content)) throw new TypeError(`tool ${tool.name} must return { content: [...] }`); Type guard
const isToolResult = (v) => typeof v === "object" && v !== null && Array.isArray(v.content) && v.content.every(b => b?.type === "text" && typeof b?.text === "string");
Try / catch
try { return await execute(args, signal); } catch (e) { if (String(e.message).startsWith("cave_claude_tool_result_invalid:")) { return { content: [{ type: "text", text: String(e) }] }; } throw e; } Prevention
- Wrap all tool return values in { content: [{ type: "text", text }] }
- Add a shared result-shape assertion in your tool harness
- Unit-test each tool's executor output shape
When it happens
Trigger: A harness tool's execute function returns a bare string, an object lacking a content array, or undefined instead of the { content: [...] } shape.
Common situations: Porting tools from another SDK whose result contract differs (e.g. returning plain text); a tool returning its raw domain object instead of wrapping it in content blocks.
Related errors
- cave_claude_tool_contract_unsupported
- cave_claude_tool_schema_unsupported
- AutoGen tools and workbench are mutually exclusive
- cave_budget_denomination_unavailable
- cave_claude_header_invalid
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/5d2354708e82ad89.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/claude-runtime.ts:169
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}`);
}
return value as { content: Array<{ type: "text"; text: string }> };
},
{ alwaysLoad: true },
);
});
const mcpServers: NonNullable<ClaudeSDKOptions["mcpServers"]> = {};
if (mcpTools.length > 0) {
mcpServers.caveman_agent = createSdkMcpServer({
name: "caveman_agent",
version: FRAMEWORK_VERSION,
tools: mcpTools,
alwaysLoad: true,
});
}
const sdkOptions: ClaudeSDKOptions = {
abortController: controller,
cwd: rootDir,View on GitHub (pinned to 3ee70a1026)