we-promise/sure · error · Provider::Sophtron::Error

request_failed

request_failed

Error message

Sophtron request failed: #{e.message}

What it means

Raised by Provider::Sophtron#request when the HTTP transport itself fails with SocketError, Net::OpenTimeout, or Net::ReadTimeout (HTTParty under the hood, with a 120s timeout and verified TLS). The request never completed - no status code exists. Distinguished from 137, which is the generic StandardError rescue below it.

Source

Thrown at app/models/provider/sophtron.rb:335

        parsed = parsed.with_indifferent_access
        keys.each do |key|
          return Array(parsed[key]) if parsed.key?(key)
        end
      end

      raise Error.new("Invalid Sophtron response format", :invalid_response, details: parsed)
    end

    def request(method, api_path, body: nil, parse_json: true)
      options = { headers: auth_headers(method: method, api_path: api_path) }
      options[:body] = body.to_json if body

      response = self.class.public_send(method, "#{base_url}#{api_path}", options)
      handle_response(response, parse_json: parse_json)
    rescue Error
      raise
    rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
      raise Error.new("Sophtron request failed: #{e.message}", :request_failed)
    rescue StandardError => e
      raise Error.new("Sophtron request failed: #{e.message}", :request_failed)
    end

    def auth_headers(method:, api_path:)
      {
        "Authorization" => auth_header_for(method, api_path),
        "Content-Type" => "application/json",
        "Accept" => "application/json"
      }
    end

    def handle_response(response, parse_json: true)
      body = response.body.to_s

      case response.code.to_i
      when 200, 201, 204
        return {} if body.strip.blank?

View on GitHub (pinned to e69894adb9)

Solutions

  1. Resolve/verify the base_url from the app host: curl -v <base_url>/health - SocketError means DNS/name resolution trouble
  2. Retry the operation - timeouts during heavy exports are often transient load, not config
  3. For chronic read timeouts on big exports, fetch in smaller date windows rather than raising the 120s ceiling
  4. Check whether a custom base_url was configured (normalize_base_url applied) and correct it to Sophtron's documented host
Defensive patterns

Strategy: retry

Validate before calling

require "resolv"
begin
  Resolv::DNS.open { |d| d.getresources(URI.parse(base_url).host, Resolv::DNS::Resource::IN::A) }
rescue => e
  raise "Sophtron base_url not resolvable: #{base_url}"
end

Try / catch

begin
  client.request(:get, "/Institution/allInstitutions")
rescue Provider::Sophtron::Error => e
  if e.error_type == :request_failed && e.message.match?(/SocketError|Timeout|timed out/i)
    retry_job_with_backoff # transport-level, transient
  else
    raise
  end
end

Prevention

When it happens

Trigger: DNS resolution failure for the Sophtron base_url (SocketError), connection not established within the open timeout, or the server accepted the connection but the body never arrived within the 120s read timeout (large account/transaction exports on a slow upstream).

Common situations: Typo'd or unreachable SOPHTRON_BASE_URL override; Sophtron API outage; app host DNS broken in Docker/Kubernetes; very large responses (years of transactions) exceeding even the generous read timeout on slow days.

Related errors


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