we-promise/sure · error · Error

network_error

network_error

Error message

Network error after #{max_retries} retries: #{e.message}

What it means

IndexaCapital's with_retries exhausted MAX_RETRIES=3 attempts with exponential backoff (2s base, cap 30s, jitter) on a transport error (SocketError, Net timeouts, ECONNRESET/REFUSED/ETIMEDOUT, EOFError) against https://api.indexacapital.com. It wraps any of the four public operations (list_accounts, get_holdings, get_portfolio, get_account_balance) and re-raises as Error(:network_error).

Source

Thrown at app/models/provider/indexa_capital.rb:145

      begin
        yield
      rescue *RETRYABLE_ERRORS => e
        retries += 1

        if retries <= max_retries
          delay = calculate_retry_delay(retries)
          Rails.logger.warn(
            "IndexaCapital API: #{operation_name} failed (attempt #{retries}/#{max_retries}): " \
            "#{e.class}: #{e.message}. Retrying in #{delay}s..."
          )
          sleep(delay)
          retry
        else
          Rails.logger.error(
            "IndexaCapital API: #{operation_name} failed after #{max_retries} retries: " \
            "#{e.class}: #{e.message}"
          )
          raise Error.new("Network error after #{max_retries} retries: #{e.message}", :network_error)
        end
      end
    end

    def calculate_retry_delay(retry_count)
      base_delay = INITIAL_RETRY_DELAY * (2 ** (retry_count - 1))
      jitter = base_delay * rand * 0.25
      [ base_delay + jitter, 30 ].min
    end

    def base_url
      BASE_URL
    end

    def base_headers
      {
        "Content-Type" => "application/json",
        "Accept" => "application/json"

View on GitHub (pinned to e69894adb9)

Solutions

  1. Confirm reachability from the app host: curl -v https://api.indexacapital.com/users/me (401 is fine - it proves the path works)
  2. Check HTTP_PROXY/HTTPS_PROXY env vars and DNS resolution in the runtime environment
  3. Let the scheduler retry on the next cycle; :network_error is never a credential problem, so do not lock the account
  4. If timeouts dominate, raise the HTTParty timeout or fetch per-account data in smaller jobs

Example fix

# before
provider.get_holdings(account_number: num) # raises mid-loop, kills whole sync

# after
begin
  provider.get_holdings(account_number: num)
rescue Provider::IndexaCapital::Error => e
  next if e.error_type == :network_error # log and skip, retry next cycle
  raise
end
Defensive patterns

Strategy: retry

Validate before calling

require "socket"
def indexa_reachable?(timeout: 3)
  TCPSocket.new("api.indexacapital.com", 443, connect_timeout: timeout).close
  true
rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, IO::TimeoutError
  false
end

Type guard

def indexa_network_error?(error)
  error.is_a?(Provider::IndexaCapital::Error) && error.error_type == :network_error
end

Try / catch

begin
  provider.get_portfolio(account_number: num)
rescue Provider::IndexaCapital::Error => e
  raise unless e.error_type == :network_error
  skipped << account # log and continue the batch; retry next cycle
end

Prevention

When it happens

Trigger: DNS failure resolving api.indexacapital.com; firewall blocking egress to Indexa; repeated ReadTimeouts on /accounts/{n}/portfolio for large portfolios; a proxy resetting connections from the app host.

Common situations: Containerized deploys missing allowlist entries, transient Spanish-provider network issues outlasting the ~6s of cumulative backoff, local dev behind a VPN/proxy that resets long-lived TLS sessions.

Related errors


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