we-promise/sure · error · Provider::Simplefin::SimplefinError

request_failed

request_failed

Error message

Exception during #{operation_name}: #{e.message}

What it means

The catch-all rescue in Provider::Simplefin.with_retries: any exception that is not a SimplefinError and not a retryable network error is logged as 'non-retryable' and re-raised wrapped in SimplefinError(:request_failed). The original class name is only in the log line, not in the message, so the Rails log is the key diagnostic.

Source

Thrown at app/models/provider/simplefin.rb:165

          sleep(delay) if backoff && delay.to_f.positive?
          retry
        else
          Rails.logger.error(
            "SimpleFin API: #{operation_name} failed after #{max_retries} retries: " \
            "#{e.class}: #{e.message}"
          )
          raise SimplefinError.new(
            "Network error after #{max_retries} retries: #{e.message}",
            :network_error
          )
        end
      rescue SimplefinError => e
        # Preserve original error type and message.
        raise
      rescue => e
        # Non-retryable errors are logged and re-raised immediately
        Rails.logger.error "SimpleFin API: #{operation_name} failed with non-retryable error: #{e.class}: #{e.message}"
        raise SimplefinError.new("Exception during #{operation_name}: #{e.message}", :request_failed)
      end
    end

    # Calculate delay with exponential backoff and jitter
    def calculate_retry_delay(retry_count)
      # Exponential backoff: 2^retry * initial_delay
      base_delay = INITIAL_RETRY_DELAY * (2 ** (retry_count - 1))
      # Add jitter (0-25% of base delay) to prevent thundering herd
      jitter = base_delay * rand * 0.25
      # Cap at max delay
      [ base_delay + jitter, MAX_RETRY_DELAY ].min
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Grep the log for 'failed with non-retryable error' - the e.class recorded there (e.g. OpenSSL::SSL::SSLError vs JSON::ParserError) identifies the family
  2. For SSL errors, verify the cert chain with curl -v and check whether a proxy intercepts TLS; do not disable verification
  3. For parse/type errors, reproduce the raw request with curl and inspect the actual response body/content-type
  4. Fix the root cause rather than retrying - this wrapper deliberately does not retry because these errors are deterministic
Defensive patterns

Strategy: try-catch

Try / catch

begin
  simplefin.get_accounts
rescue Provider::Simplefin::SimplefinError => e
  if e.error_type == :request_failed
    report_with_context(e.message) # deterministic bug or env issue - do not retry
  else
    raise
  end
end

Prevention

When it happens

Trigger: Non-network failures inside the HTTP operation: JSON::ParserError while parsing a 2xx body, OpenSSL::SSL::SSLError from a bad TLS handshake/cert, TypeError/ArgumentError from a malformed base_url (e.g. access URL stored with spaces), or any unexpected bug in the request block.

Common situations: Access URL saved with a typo that survives URI parsing but breaks later; a TLS-intercepting corporate proxy presenting an untrusted cert; SimpleFIN returning HTML instead of JSON on a 200; code changes introducing a NoMethodError inside the block.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/e46f1b80eaf9f4e8. Report an issue: GitHub.