we-promise/sure · error · Provider::Openai::Error
Invalid JSON in native categorization: #{e.message}
Error message
Invalid JSON in native categorization: #{e.message} What it means
Raised by Provider::Openai::AutoCategorizer#extract_categorizations_native when the native Responses-API message text exists but JSON.parse fails on it. The native path assumes strict JSON (the request asks for JSON output), unlike the generic path which uses parse_json_flexibly to strip code fences and prose. Any deviation — markdown fences around the JSON, a leading sentence, or truncated JSON — produces JSON::ParserError whose message is embedded into the raised error.
Source
Thrown at app/models/provider/openai/auto_categorizer.rb:351
variations.each do |_key, synonyms|
if synonyms.include?(input_lower) && synonyms.include?(category_lower)
return true
end
end
false
end
def extract_categorizations_native(response)
# Find the message output (not reasoning output)
message_output = response["output"]&.find { |o| o["type"] == "message" }
raw = message_output&.dig("content", 0, "text")
raise Provider::Openai::Error, "No message content found in response" if raw.nil?
JSON.parse(raw).dig("categorizations")
rescue JSON::ParserError => e
raise Provider::Openai::Error, "Invalid JSON in native categorization: #{e.message}"
end
def extract_categorizations_generic(response)
raw = response.dig("choices", 0, "message", "content")
parsed = parse_json_flexibly(raw)
# Handle different response formats from various LLMs
categorizations = parsed.dig("categorizations") ||
parsed.dig("results") ||
(parsed.is_a?(Array) ? parsed : nil)
raise Provider::Openai::Error, "Could not find categorizations in response" if categorizations.nil?
# Normalize field names (some LLMs use different naming)
categorizations.map do |cat|
{
"transaction_id" => cat["transaction_id"] || cat["id"] || cat["txn_id"],
"category_name" => cat["category_name"] || cat["category"] || cat["name"]View on GitHub (pinned to e69894adb9)
Solutions
- Route the response through the generic extractor's parse_json_flexibly (strip fences/prose) when native JSON.parse fails, instead of raising immediately.
- Ensure the native request actually sets strict JSON schema/response_format and that the endpoint honors it (first-party OpenAI does).
- Increase the response token budget so JSON is not truncated.
- Tighten the prompt: 'Respond with a single JSON object, no markdown, no commentary.'
Example fix
# before
JSON.parse(raw).dig("categorizations")
rescue JSON::ParserError => e
raise Provider::Openai::Error, "Invalid JSON in native categorization: #{e.message}"
# after
begin
JSON.parse(raw).dig("categorizations")
rescue JSON::ParserError => e
parsed = parse_json_flexibly(raw) # reuses fence/prose stripping
raise Provider::Openai::Error, "Invalid JSON in native categorization: #{e.message}" if parsed.nil?
parsed.dig("categorizations")
end Defensive patterns
Strategy: fallback
Try / catch
begin JSON.parse(raw) rescue JSON::ParserError parsed = parse_json_flexibly(raw) # generic fence/prose stripping as second chance raise if parsed.nil? parsed end
Prevention
- Ensure the endpoint truly enforces JSON mode before relying on strict JSON.parse on model text.
- Route custom gateways through the generic extractor, which tolerates fences and prose.
- Include a literal JSON example in the prompt to anchor the output format.
When it happens
Trigger: Model wraps the JSON in ```json fences despite response_format; model prefixes 'Here is the JSON:' prose; JSON truncated mid-object because output tokens ran out; custom OpenAI-compatible provider ignoring strict JSON mode and emitting thinking prose.
Common situations: Routing non-OpenAI models (via uri_base) through the native responses path where JSON mode is not enforced; low max_response_tokens on large transaction batches; prompt changes that make the model chatty; gateway middleware appending text.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Could not find categorizations in response
- Could not parse JSON from response: #{raw.truncate(200)}
- Could not parse JSON from PDF processing response: #{raw.tru
- No categories available for auto-categorization
- No message content found in response
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/500e7efda09f0ae7.
Report an issue: GitHub.