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

  1. Return { content: [{ type: "text", text: "..." }] } from the tool's execute
  2. Wrap existing return values at the end of the executor: return { content: [{ type: "text", text: JSON.stringify(value) }] }
  3. 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

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


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)