we-promise/sure · error · Provider::Anthropic::Error
Tool call missing merchants
Error message
Tool call missing merchants
What it means
The model invoked report_merchants, but its parsed input is not a Hash containing a "merchants" Array (string inputs are JSON.parse'd; string and symbol keys both accepted). "Tool call missing merchants" means the tool arguments came back with the wrong shape — a bare array, a renamed key, or truncated JSON — so no merchant list can be built.
Source
Thrown at app/models/provider/anthropic/auto_merchant_detector.rb:141
```
Transactions to analyze:
```json
#{transactions.to_json}
```
MESSAGE
end
def extract_merchants(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)
merchants = input.is_a?(Hash) ? (input["merchants"] || input[:merchants]) : nil
raise Provider::Anthropic::Error, "Tool call missing merchants" unless merchants.is_a?(Array)
merchants
end
def build_response(merchants)
merchants.map do |m|
AutoDetectedMerchant.new(
transaction_id: m["transaction_id"] || m[:transaction_id],
business_name: normalize_merchant_name(m["business_name"] || m[:business_name]),
business_url: normalize_value(m["business_url"] || m[:business_url])
)
end
end
def normalize_value(value)
return nil if value.nil?
str = value.to_s.strip
return nil if str.empty? || str.casecmp("null").zero?
strView on GitHub (pinned to e69894adb9)
Solutions
- Retry — isolated malformed args are typically transient.
- Make the tool schema strict: require the "merchants" property with item types so the model must use the exact key.
- Upgrade to a current model (claude-sonnet-4-6 class) and reduce batch size to prevent truncated arguments.
- Inspect the tool input in the Langfuse trace to see the exact shape the model sent.
Example fix
# tool schema — before
{ type: "object", properties: { merchants: { type: "array" } } } # not required
# model omits "merchants" => "Tool call missing merchants"
# after
{ type: "object", required: ["merchants"],
properties: { merchants: { type: "array", items: { type: "object",
properties: { transaction_id: { type: "string" }, business_name: { type: "string" }, business_url: { type: "string" } } } } } } Defensive patterns
Strategy: retry
Try / catch
attempts = 0
begin
result = detector.auto_detect_merchants
rescue Provider::Anthropic::Error => e
attempts += 1
retry if attempts < 2 && e.message.include?("missing merchants")
raise
end Prevention
- Mark "merchants" required in the tool input schema with typed items.
- Smaller batches reduce truncated tool arguments.
- Use Langfuse traces to catch systematic schema drift early.
When it happens
Trigger: Model returns {"detected_merchants": [...]} or a top-level array instead of {"merchants": [...]}; tool arguments get cut at max_tokens and JSON.parse yields a partial Hash; a proxy rewrites tool input keys.
Common situations: Loose tool schema letting the model pick its own key name; older/smaller models deviating from schema; one-off malformed responses; gateway middleware normalizing JSON keys (e.g. camelize).
Related errors
- Tool call missing categorizations
- Model did not invoke #{TOOL_NAME}
- Tool call missing merchants
- Too many transactions to auto-detect merchants. Max is 25 pe
- Model did not invoke #{TOOL_NAME}
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/25fc273fdfe23fe8.
Report an issue: GitHub.