wavetermdev/waveterm · error
tool input is not an object
Error message
tool input is not an object
What it means
The Anthropic tool_use contract requires tool input to be a JSON object. FinalObject parses the accumulated stream successfully but the top-level value is not an object (e.g. an array, string, or number), so it refuses to return it as tool input. This is a defensive check against provider/tool-schema mismatches.
Source
Thrown at pkg/aiusechat/anthropic/anthropic-backend.go:342
func (p *partialJSON) Bytes() []byte { return p.buf.Bytes() }
func (p *partialJSON) FinalObject() (json.RawMessage, error) {
raw := p.buf.Bytes()
// If empty, treat as "{}"
if len(bytes.TrimSpace(raw)) == 0 {
return json.RawMessage(`{}`), nil
}
// The accumulated content should be a valid JSON object string; parse it.
var v interface{}
if err := json.Unmarshal(raw, &v); err != nil {
return nil, fmt.Errorf("invalid accumulated tool input JSON: %w", err)
}
// Ensure it's an object per Anthropic contract
switch v.(type) {
case map[string]interface{}:
return json.RawMessage(raw), nil
default:
return nil, fmt.Errorf("tool input is not an object")
}
}
// sanitizeHostnameInError removes the Wave cloud hostname from error messages
func sanitizeHostnameInError(err error) error {
if err == nil {
return nil
}
errStr := err.Error()
parsedURL, parseErr := url.Parse(uctypes.DefaultAIEndpoint)
if parseErr == nil && parsedURL.Host != "" && strings.Contains(errStr, parsedURL.Host) {
errStr = strings.ReplaceAll(errStr, uctypes.DefaultAIEndpoint, "AI service")
errStr = strings.ReplaceAll(errStr, parsedURL.Host, "host")
}
return fmt.Errorf("%s", errStr)
}
// makeThinkingOpts creates thinking options based on level and max tokensView on GitHub (pinned to a4447c1563)
Solutions
- Fix the tool's input_schema so it declares type: object with defined properties.
- Wrap the call and return a clear tool_result error back to the model so it can self-correct on the next turn.
- Check Anthropic API/model version changes affecting tool input emission.
- Log the raw accumulated JSON (json.RawMessage) to see what the model actually produced.
Example fix
// before
schema := map[string]any{"type": "array", "items": ...} // non-object input
// after
schema := map[string]any{"type": "object", "properties": map[string]any{"items": map[string]any{"type": "array"}} // object root Defensive patterns
Strategy: type-guard
Validate before calling
func isJSONObject(raw json.RawMessage) bool {
var m map[string]interface{}
return json.Unmarshal(raw, &m) == nil
} Type guard
func asObject(raw json.RawMessage) (map[string]interface{}, bool) {
var m map[string]interface{}
if err := json.Unmarshal(raw, &m); err != nil { return nil, false }
return m, true
} Try / catch
input, err := acc.FinalObject()
if err != nil {
if strings.Contains(err.Error(), "not an object") {
return emitToolResultError("tool returned non-object input") // let model retry
}
return err
} Prevention
- Declare tool input_schema with type: object and explicit properties
- Return a tool_result error to the model so it can correct its arguments next turn
- Log the raw JSON to spot models repeatedly violating the schema
- Test tool definitions against the Anthropic tool-use API before shipping
When it happens
Trigger: A tool_use content block whose accumulated input_json_delta stream parses to valid JSON but with a non-object top-level type — e.g. the model emitted "[1,2]" or "null" as tool arguments.
Common situations: Tool schemas declared without a proper object input_schema (parameters as arrays or free types); a model misbehaving on a poorly-described tool; version changes in the Anthropic API emitting alternative structures.
Related errors
- invalid accumulated tool input JSON: %w
- tool result missing ToolUseID
- tool call with ID %s not found in chat %s
- msg.error
- sse handler is nil
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/d069aab98f748da4.
Report an issue: GitHub.