{"record":{"id":"2d9198d16164f0d7","repo":"we-promise/sure","slug":"failed-to-fetch-exchange-rates-rates-response-e","errorCode":null,"errorMessage":"Failed to fetch exchange rates: #{rates_response.error.message}","messagePattern":"Failed to fetch exchange rates: #(.+?)","errorType":"exception","errorClass":"Provider::YahooFinance::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/yahoo_finance.rb","lineNumber":146,"sourceCode":"      if from == to\n        Rate.new(date: date, from: from, to: to, rate: 1.0)\n      else\n        cache_key = \"exchange_rate_#{from}_#{to}_#{date}\"\n        if cached_result = get_cached_result(cache_key)\n          cached_result\n        else\n          # For a single date, we'll fetch a range and find the closest match\n          end_date = date\n          start_date = date - 10.days # Extended range for better coverage\n\n          rates_response = fetch_exchange_rates(\n            from: from,\n            to: to,\n            start_date: start_date,\n            end_date: end_date\n          )\n\n          raise Error, \"Failed to fetch exchange rates: #{rates_response.error.message}\" unless rates_response.success?\n\n          rates = rates_response.data\n          if rates.length == 1\n            rates.first\n          else\n            # Find the exact date or the closest previous date\n            target_rate = rates.find { |r| r.date == date } ||\n                         rates.select { |r| r.date <= date }.max_by(&:date)\n\n            raise Error, \"No exchange rate found for #{from}/#{to} on or before #{date}\" unless target_rate\n\n            cache_result(cache_key, target_rate)\n            target_rate\n          end\n        end\n      end\n    end\n  end","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/yahoo_finance.rb#L128-L164","documentation":"Provider::YahooFinance::Error raised inside fetch_exchange_rate when the delegated fetch_exchange_rates call (a 10-day window ending at the target date) returned a failed ProviderResponse. It re-raises the underlying error's message — the actual cause is whatever made the range fetch fail (rate limit, invalid pair, parse error).","triggerScenarios":"fetch_exchange_rate(from:, to:, date:) on a cache miss triggers fetch_exchange_rates for a 10-day window; if that inner call hit Yahoo rate limiting (429), returned no chart data, or returned invalid JSON, this wrapper error carries that message.","commonSituations":"Burst of exchange-rate lookups during a multi-currency portfolio sync exhausting Yahoo's unofficial rate limit; exotic pairs Yahoo doesn't quote; Yahoo changing response shape so parsing fails.","solutions":["Inspect the tail of the message — it names the real failure ('No chart data found...', 'Invalid response format...', rate-limit text); fix that root cause","For rate-limit messages, back off and retry; check provider.health_status which tracks Yahoo's rate_limited state with 30-min freshness","For 'No chart data found', verify the pair on finance.yahoo.com and use an inverse or cross pair via USD","Add caching/TTL for repeated single-date lookups to reduce call volume"],"exampleFix":"// before\nrate = provider.fetch_exchange_rate(from: \"EUR\", to: \"USD\", date: Date.yesterday)\n\n// after\nbegin\n  rate = provider.fetch_exchange_rate(from: \"EUR\", to: \"USD\", date: Date.yesterday)\nrescue Provider::YahooFinance::Error => e\n  if provider.health_status == :rate_limited\n    RetryableSyncJob.perform_later(wait: 10.minutes)\n  else\n    Rails.logger.warn(\"Yahoo FX failed: #{e.message}\")\n    rate = fallback_provider.fetch_exchange_rate(from: \"EUR\", to: \"USD\", date: Date.yesterday)\n  end\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  provider.fetch_exchange_rate(from:, to:, date:)\nrescue Provider::YahooFinance::Error => e\n  # message wraps the real cause; branch on health state\n  if provider.health_status == :rate_limited\n    retry_later(wait: 30.minutes)\n  else\n    fallback_provider.fetch_exchange_rate(from:, to:, date:)\n  end\nend","preventionTips":["Check provider.health_status before batches — it tracks Yahoo rate_limited/unavailable with TTLs","Cache single-date FX results so repeated syncs don't re-trigger the 10-day window fetch","Keep a secondary FX provider wired for resilience"],"tags":["ruby","yahoo-finance","exchange-rate","wrapped-error","api"],"backgroundTag":"provider-request-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}