{"record":{"id":"1395264a65d9c2d3","repo":"we-promise/sure","slug":"category-ref-not-found-use-get-categories-to","errorCode":null,"errorMessage":"Category '#{ref}' not found. Use get_categories to list categories.","messagePattern":"Category '#(.+?)' not found\\. Use get_categories to list categories\\.","errorType":"exception","errorClass":"Assistant::Error","httpStatus":null,"severity":"warning","filePath":"app/models/assistant/function/update_budget.rb","lineNumber":167,"sourceCode":"      value = Float(raw)\n      raise Assistant::Error, \"#{label} must be a non-negative number.\" if !value.finite? || value.negative?\n      value\n    rescue ArgumentError, TypeError\n      raise Assistant::Error, \"#{label} must be a non-negative number.\"\n    end\n\n    def find_budget_category!(budget, ref)\n      ref = ref.to_s.strip\n      raise Assistant::Error, \"Each categories entry needs a category name or id.\" if ref.blank?\n\n      category = valid_uuid?(ref) ? family.categories.find_by(id: ref) : nil\n      category ||= family.categories.where(\"LOWER(name) = ?\", ref.downcase).first\n\n      if category.nil?\n        if Category.all_uncategorized_names.any? { |name| name.casecmp?(ref) }\n          raise Assistant::Error, \"'#{ref}' is the unallocated remainder of budgeted_spending and cannot be set directly. Adjust budgeted_spending or category amounts instead.\"\n        end\n        raise Assistant::Error, \"Category '#{ref}' not found. Use get_categories to list categories.\"\n      end\n\n      budget.budget_categories.find_by(category_id: category.id) ||\n        raise(Assistant::Error, \"No budget row exists for category '#{category.name}' in #{budget.to_param}.\")\n    end\n\n    def format_money(value)\n      Money.new(value || 0, family.currency).format\n    end\n\n    def error(key, message)\n      { success: false, error: key, message: message }\n    end\nend\n","sourceCodeStart":149,"sourceCodeEnd":182,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/assistant/function/update_budget.rb#L149-L182","documentation":"Raised by find_budget_category! when no category of the family matches the reference: the UUID branch (valid_uuid? then family.categories.find_by(id:)) and the case-insensitive exact-name branch (LOWER(name) = ref.downcase) both miss. The message points the caller at get_categories because the resolution requires an exact name or the real id.","triggerScenarios":"update_budget with category: \"Food & Dining\" when the family's category is named \"Groceries\"; a category id from another family (UUID parses but find_by scopes to family.categories); a typo'd or translated name; a category that was renamed or deleted between get_budget and update_budget.","commonSituations":"LLM hallucinating plausible category names instead of reading get_categories/get_budget output first; concurrent renames; case variants are fine (matching is case-insensitive) but synonyms, plurals, and partial matches are not (\"Grocer\" fails).","solutions":["Call get_categories (or get_budget) first and pass back the exact name or id it returned — that round-trip is the intended flow.","If the category genuinely should exist, create it via the normal category API before budgeting it.","Check for renames/deletions if a previously working name suddenly fails.","Never cross-pollinate ids between families — the lookup is scoped to family.categories."],"exampleFix":"# before\nupdate_budget.call({ \"categories\" => [{ \"category\" => \"Food\", \"amount\" => 500 }] })\n\n# after — resolve the real name/id first\ncats = get_categories.call({}) # => [{ \"name\" => \"Groceries\", \"id\" => \"0198...\" }], ...\nupdate_budget.call({ \"categories\" => [{ \"category\" => cats.first[\"id\"], \"amount\" => 500 }] })","handlingStrategy":"validation","validationCode":"def resolvable_category?(family, ref)\n  return true if family.categories.exists?(id: ref)\n  family.categories.exists?(\"LOWER(name) = ?\", ref.to_s.downcase)\nend","typeGuard":"# Ruby\ndef existing_category(family, ref)\n  family.categories.find_by(id: ref) || family.categories.where(\"LOWER(name) = ?\", ref.to_s.downcase).first # nil when not found\nend","tryCatchPattern":null,"preventionTips":["Always call get_categories/get_budget first and pass back names/ids verbatim — no synonyms, no plurals, no partial matches.","Scope lookups to family.categories; ids from another family will never resolve.","Watch for concurrent renames/deletes if a previously working name starts failing."],"tags":["budget","category","not-found","assistant","tool-call"],"backgroundTag":"record-not-found","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}