we-promise/sure · error · Provider::Anthropic::Error
Tool call missing categorizations
Error message
Tool call missing categorizations
What it means
The model did invoke the report_categorizations tool, but block_input parsed to a Hash without a usable "categorizations" Array (string inputs are JSON.parse'd first, symbol and string keys are both accepted). This raises "Tool call missing categorizations" — the tool was called but its arguments have the wrong shape, e.g. a bare array, a differently-named key, or an empty payload.
Source
Thrown at app/models/provider/anthropic/auto_categorizer.rb:139
```
Auto-categorize the following transactions:
```json
#{transactions.to_json}
```
MESSAGE
end
def extract_categorizations(response)
tool_use = Array(response.content).find { |block| block_type(block) == :tool_use }
raise Provider::Anthropic::Error, "Model did not invoke #{TOOL_NAME}" unless tool_use
input = block_input(tool_use)
input = JSON.parse(input) if input.is_a?(String)
categorizations = input.is_a?(Hash) ? (input["categorizations"] || input[:categorizations]) : nil
raise Provider::Anthropic::Error, "Tool call missing categorizations" unless categorizations.is_a?(Array)
categorizations
end
def build_response(categorizations)
categorizations.map do |c|
category_name = c["category_name"] || c[:category_name]
AutoCategorization.new(
transaction_id: c["transaction_id"] || c[:transaction_id],
category_name: normalize_category(category_name)
)
end
end
def normalize_category(value)
return nil if value.nil?
str = value.to_s.strip
return nil if str.empty? || str.casecmp("null").zero?
View on GitHub (pinned to e69894adb9)
Solutions
- Retry the request — isolated malformed tool args are usually transient.
- Tighten the tool's input schema for the categorizations field (type, required) so the model is steered to the exact key.
- Switch to a current, stronger model (claude-sonnet-4-6 class) known to follow the tool schema; lower batch size to avoid truncated arguments.
- Compare the raw tool arguments in the Langfuse trace against the expected schema to find the exact divergence.
Example fix
# tool definition (conceptual) — before
{ name: "report_categorizations", input_schema: { type: "object" } }
# model returns { "results": [...] } => "Tool call missing categorizations"
# after
{ name: "report_categorizations",
input_schema: { type: "object", required: ["categorizations"],
properties: { categorizations: { type: "array", items: { type: "object" } } } } } Defensive patterns
Strategy: retry
Try / catch
attempts = 0
begin
result = categorizer.auto_categorize
rescue Provider::Anthropic::Error => e
attempts += 1
retry if attempts < 2 && e.message.include?("missing categorizations")
raise
end Prevention
- Make the tool schema strict: categorizations required, items typed.
- Pin a current model; weaker models drift from schemas more often.
- Compare the raw tool input in Langfuse against the schema when it repeats — fix the schema, not the retry count.
When it happens
Trigger: The model calls the tool with {"results": [...]} or a top-level array instead of {"categorizations": [...]}; a partially-truncated tool argument at the max_tokens boundary parses to an incomplete Hash; a proxy/gateway rewrites or mangles tool input JSON.
Common situations: Schema drift between the tool definition and what a newer/older model produces; forced tool_choice with an under-specified tool schema; flaky one-off malformed output that a retry fixes; gateways that re-serialize tool arguments and drop keys.
Related errors
- Model did not invoke #{TOOL_NAME}
- Tool call missing merchants
- Tool call missing merchants
- Too many transactions to auto-categorize. Max is 25 per requ
- No categories available for auto-categorization
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/ae6c3fcee02588cf.
Report an issue: GitHub.