we-promise/sure · warning · Assistant::Error
Each categories entry needs a category name or id.
Error message
Each categories entry needs a category name or id.
What it means
Raised by Assistant::Function::UpdateBudget#find_budget_category! when a categories entry carries no usable category reference. ref comes from change["category"] when the entry is a Hash, or nil when it is not a Hash at all (update_budget.rb:115); after to_s.strip it must be non-blank.
Source
Thrown at app/models/assistant/function/update_budget.rb:158
}
rescue Assistant::Error => e
error("invalid_params", e.message)
rescue ActiveRecord::RecordInvalid => e
error("validation_failed", e.record.errors.full_messages.join("; "))
end
private
def parse_amount!(raw, label)
value = Float(raw)
raise Assistant::Error, "#{label} must be a non-negative number." if !value.finite? || value.negative?
value
rescue ArgumentError, TypeError
raise Assistant::Error, "#{label} must be a non-negative number."
end
def find_budget_category!(budget, ref)
ref = ref.to_s.strip
raise Assistant::Error, "Each categories entry needs a category name or id." if ref.blank?
category = valid_uuid?(ref) ? family.categories.find_by(id: ref) : nil
category ||= family.categories.where("LOWER(name) = ?", ref.downcase).first
if category.nil?
if Category.all_uncategorized_names.any? { |name| name.casecmp?(ref) }
raise Assistant::Error, "'#{ref}' is the unallocated remainder of budgeted_spending and cannot be set directly. Adjust budgeted_spending or category amounts instead."
end
raise Assistant::Error, "Category '#{ref}' not found. Use get_categories to list categories."
end
budget.budget_categories.find_by(category_id: category.id) ||
raise(Assistant::Error, "No budget row exists for category '#{category.name}' in #{budget.to_param}.")
end
def format_money(value)
Money.new(value || 0, family.currency).format
endView on GitHub (pinned to e69894adb9)
Solutions
- Shape every entry as { "category" => <name or id>, "amount" => <number> } — both keys present, category non-empty.
- If you aggregate caller-side input, validate entries with an each-check before invoking the tool.
- Give the LLM the exact object shape in the prompt or fix the tool description example if drift persists.
Example fix
# before
update_budget.call({ "categories" => ["Groceries"] })
# after
update_budget.call({ "categories" => [{ "category" => "Groceries", "amount" => 900 }] }) Defensive patterns
Strategy: validation
Validate before calling
params["categories"].to_a.each do |entry|
unless entry.is_a?(Hash) && entry["category"].to_s.strip.present?
raise ArgumentError, "each categories entry needs a non-empty category (name or id)"
end
end Type guard
# Ruby def valid_category_entry?(entry) entry.is_a?(Hash) && entry["category"].is_a?(String) && entry["category"].strip.present? && !entry["amount"].nil? end
Prevention
- Always build entries as { "category" => name_or_id, "amount" => number } with both keys present.
- Validate the array shape caller-side before invoking update_budget — one malformed entry aborts the whole transaction's work.
- If the LLM emits bare strings or missing keys, fix the prompt/tool description example rather than catching after the fact.
When it happens
Trigger: update_budget called with categories: [{ "amount" => 100 }] (no "category" key), categories: [{ "category" => " ", "amount" => 100 }] (whitespace-only), or categories: ["Groceries"] (a plain string entry — not a Hash — so ref becomes nil). All reach the blank check at app/models/assistant/function/update_budget.rb:158.
Common situations: LLM emitting shorthand array entries instead of { category:, amount: } objects; schema-drift between what the model was told and what it produced; hand-written tool-call JSON missing the required property (the schema lists both keys required but strict_mode? is false).
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- #{label} must be a non-negative number.
- '#{ref}' is the unallocated remainder of budgeted_spending a
- Invalid month: #{raw}. Use YYYY-MM or MMM-YYYY.
- Category '#{ref}' not found. Use get_categories to list cate
- Assistant exceeded the tool-call limit of #{max_tool_call_it
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/8b20fa665584c808.
Report an issue: GitHub.