{"record":{"id":"9634a46ea8fb5fc7","repo":"we-promise/sure","slug":"invalid-month-raw-use-yyyy-mm-or-mmm-yyyy","errorCode":null,"errorMessage":"Invalid month: #{raw}. Use YYYY-MM or MMM-YYYY.","messagePattern":"Invalid month: #(.+?)\\. Use YYYY-MM or MMM-YYYY\\.","errorType":"exception","errorClass":"Assistant::Error","httpStatus":null,"severity":"warning","filePath":"app/models/assistant/function/month_resolvable.rb","lineNumber":23,"sourceCode":"  private\n    def resolve_month_start(raw)\n      base = parse_month(raw)\n      return (base || Date.current).beginning_of_month unless family.uses_custom_month_start?\n\n      # Match Budget.param_to_date for explicit slugs so the input round-trips with the response.\n      base ? Date.new(base.year, base.month, family.month_start_day) : family.custom_month_start_for(Date.current)\n    end\n\n    def parse_month(raw)\n      return nil if raw.blank?\n\n      # Date.strptime ignores trailing characters, so guard with strict anchors first.\n      fmt = case raw\n      when /\\A\\d{4}-\\d{2}\\z/         then \"%Y-%m\"\n      when /\\A[A-Za-z]{3}-\\d{4}\\z/   then \"%b-%Y\"\n      end\n\n      raise Assistant::Error, \"Invalid month: #{raw}. Use YYYY-MM or MMM-YYYY.\" if fmt.nil?\n\n      Date.strptime(raw, fmt)\n    rescue ArgumentError\n      raise Assistant::Error, \"Invalid month: #{raw}. Use YYYY-MM or MMM-YYYY.\"\n    end\nend\n","sourceCodeStart":5,"sourceCodeEnd":30,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/assistant/function/month_resolvable.rb#L5-L30","documentation":"Raised by Assistant::Function::MonthResolvable#parse_month when the month argument does not strictly match \\A\\d{4}-\\d{2}\\z (YYYY-MM) or \\A[A-Za-z]{3}-\\d{4}\\z (MMM-YYYY). The anchor check exists because Date.strptime ignores trailing characters (\"2026-08-15\" would silently parse with %Y-%m), so any other shape is rejected up front. UpdateBudget rescues Assistant::Error and returns { success: false, error: \"invalid_params\", message: ... }.","triggerScenarios":"Calling update_budget with month: \"2026/08\" (slash), \"Aug 2026\" (space instead of hyphen), \"2026-8\" (single-digit month), \"202608\", \"last month\", or \"August 2026\" — none match either anchored regex, so fmt is nil and the raise at app/models/assistant/function/month_resolvable.rb:23 fires.","commonSituations":"LLM free-forming a natural-language month instead of the documented format; user-typed values passed through unnormalized; months imported from other tools that use MM/DD or 'August 2026' styles.","solutions":["Pass the month in one of the two accepted shapes: \"2026-08\" or \"Aug-2026\" (three-letter abbreviation, hyphen, four-digit year).","If the value comes from a Date object, normalize it first with date.strftime(\"%Y-%m\") before calling the function.","If you control the caller, pre-validate with the same anchored regexes and reformat instead of retrying blindly."],"exampleFix":"# before\nupdate_budget.call({ \"month\" => \"August 2026\", \"budgeted_spending\" => 6500 })\n\n# after — normalize to the accepted slug first\nmonth = Date.parse(\"August 2026\").strftime(\"%Y-%m\")\nupdate_budget.call({ \"month\" => month, \"budgeted_spending\" => 6500 })","handlingStrategy":"validation","validationCode":"MONTH_SLUG = /\\A(?:\\d{4}-\\d{2}|[A-Za-z]{3}-\\d{4})\\z/\nraise ArgumentError, \"month must be YYYY-MM or MMM-YYYY\" unless month.to_s.match?(MONTH_SLUG)","typeGuard":"# Ruby\ndef valid_month_slug?(raw)\n  raw.is_a?(String) && raw.match?(\"\\\\A(?:\\\\d{4}-\\\\d{2}|[A-Za-z]{3}-\\\\d{4})\\\\z\")\nend","tryCatchPattern":"begin\n  update_budget.call(params)\nrescue Assistant::Error => e\n  # call() already converts this to { success: false, error: \"invalid_params\" } — check the result instead\nend","preventionTips":["Always derive month slugs from Date#strftime(\"%Y-%m\") instead of hand-formatting strings.","When passing user-typed months, normalize via Date.parse(...).strftime('%Y-%m') inside a rescue before calling the tool.","Feed the LLM the exact accepted formats in the prompt and echo them back from get_budget so round-trips stay canonical."],"tags":["validation","date","assistant","tool-call","input-format"],"backgroundTag":"invalid-date-format","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}