{"record":{"id":"de0cbe82ba7e610a","repo":"we-promise/sure","slug":"class-name-api-operation-name-failed-a","errorCode":null,"errorMessage":"<%= class_name %> API: #{operation_name} failed (attempt #{retries}/#{max_retries}): #{e.class}: #{e.message}. Retrying in #{delay}s...","messagePattern":"<%= class_name %> API: #(.+?) failed \\(attempt #(.+?)/#(.+?)\\): #(.+?): #(.+?)\\. Retrying in #(.+?)s\\.\\.\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/generators/provider/family/templates/provider_sdk.rb.tt","lineNumber":144,"sourceCode":"    INITIAL_RETRY_DELAY = 2 # seconds\n\n    def validate_configuration!\n<% secret_fields.each do |field| -%>\n      raise ConfigurationError, \"<%= field[:name].humanize %> is required\" if @<%= field[:name] %>.blank?\n<% end -%>\n    end\n\n    def with_retries(operation_name, max_retries: MAX_RETRIES)\n      retries = 0\n\n      begin\n        yield\n      rescue *RETRYABLE_ERRORS => e\n        retries += 1\n\n        if retries <= max_retries\n          delay = calculate_retry_delay(retries)\n          Rails.logger.warn(\n            \"<%= class_name %> API: #{operation_name} failed (attempt #{retries}/#{max_retries}): \" \\\n            \"#{e.class}: #{e.message}. Retrying in #{delay}s...\"\n          )\n          sleep(delay)\n          retry\n        else\n          Rails.logger.error(\n            \"<%= class_name %> API: #{operation_name} failed after #{max_retries} retries: \" \\\n            \"#{e.class}: #{e.message}\"\n          )\n          raise Error.new(\"Network error after #{max_retries} retries: #{e.message}\", :network_error)\n        end\n      end\n    end\n\n    def calculate_retry_delay(retry_count)\n      base_delay = INITIAL_RETRY_DELAY * (2 ** (retry_count - 1))\n      jitter = base_delay * rand * 0.25","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/lib/generators/provider/family/templates/provider_sdk.rb.tt#L126-L162","documentation":"Inside the generated provider SDK's with_retries wrapper: a call that raised one of RETRYABLE_ERRORS is about to be retried. The warning reports attempt N of max_retries, the exception class/message, and the computed backoff delay, then sleeps and retries the yield. This message is transient by design; the terminal condition is the sibling error log \"failed after N retries\" followed by raising Error with :network_error after exhaustion.","triggerScenarios":"Any RETRYABLE_ERRORS member raised by the wrapped SDK call: connection resets (Errno::ECONNRESET), timeouts (Net::OpenTimeout/ReadTimeout), 5xx-driven exceptions depending on the constant's contents — each occurrence during a single operation increments the attempt counter and logs this line (provider_sdk.rb.tt:144 region).","commonSituations":"Provider API outage or degraded performance; rate limiting that surfaces as timeouts; restrictive egress rules/proxies causing intermittent resets; retry storms when many jobs retry simultaneously during provider downtime because sleep blocks the worker thread.","solutions":["Check whether the follow-up 'failed after N retries' error line appears — if yes, treat it as a provider outage/rate limit and investigate that; if no, the retry succeeded and no action is needed","Inspect the exception class in the message: ECONNRESET/timeouts suggest network or provider health; rate-limit errors mean tuning backoff or honoring Retry-After","Tune max_retries and calculate_retry_delay (add jitter) in the generated SDK so retries do not hammer a recovering provider","For persistent issues, verify the API base URL and credentials, and check the provider status page"],"exampleFix":"# generated provider_sdk.rb - before\ndelay = calculate_retry_delay(retries)\nsleep(delay)\n\n# after (full jitter to avoid thundering herd)\ndelay = calculate_retry_delay(retries)\nsleep(rand * delay)","handlingStrategy":"retry","validationCode":"# Pre-flight the provider endpoint before batching many SDK calls\nrequire \"socket\"\ndef provider_reachable?(host, port = 443, timeout = 2)\n  Socket.tcp(host, port, connect_timeout: timeout).close\n  true\nrescue StandardError\n  false\nend","typeGuard":"def retryable?(exception)\n  RETRYABLE_ERRORS.any? { |klass| exception.is_a?(klass) }\nend","tryCatchPattern":"# Keep retry semantics but classify outcomes explicitly\nrescue *RETRYABLE_ERRORS => e\n  retries += 1\n  if retries <= max_retries\n    delay = calculate_retry_delay(retries)\n    Rails.logger.warn(\"... Retrying in #{delay}s...\")\n    sleep(rand * delay) # jitter\n    retry\n  else\n    raise Error.new(\"Network error after #{max_retries} retries: #{e.message}\", :network_error)\n  end\nend","preventionTips":["Add jitter to calculate_retry_delay so concurrent jobs don't retry in lockstep during provider outages","Distinguish rate-limit responses from network faults and honor Retry-After when the provider sends it","Cap total sleep time and push long retries to Sidekiq's own retry mechanism instead of blocking worker threads"],"tags":["retry","backoff","network","provider-api","generator-template","rate-limiting"],"backgroundTag":"retryable-api-error","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}