we-promise/sure · error · ApiError

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

Error message

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

What it means

with_retries ran the SendRequest or GetStatement call MAX_RETRIES=3 times with exponential backoff (2s base, doubling, capped 30s, +25% jitter) and every attempt raised a transport error from RETRYABLE_ERRORS: SocketError, Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT or EOFError against ndcdyn.interactivebrokers.com.

Source

Thrown at app/models/provider/ibkr_flex.rb:135

    def with_retries(operation_name, max_retries: MAX_RETRIES)
      retries = 0

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

        if retries <= max_retries
          delay = calculate_retry_delay(retries)
          Rails.logger.warn(
            "IBKR Flex: #{operation_name} failed (attempt #{retries}/#{max_retries}): #{e.class}: #{e.message}. Retrying in #{delay}s..."
          )
          sleep(delay)
          retry
        end

        raise ApiError.new("Network error after #{max_retries} retries: #{e.message}")
      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, MAX_RETRY_DELAY ].min
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Verify connectivity from the same host: curl -v https://ndcdyn.interactivebrokers.com/AccountManagement/FlexWebService/SendRequest - a TLS handshake proves DNS, routing and firewall are fine
  2. Check DNS and proxy settings for the process (resolv.conf, HTTP_PROXY/http_proxy env leaking into HTTParty)
  3. Reschedule the sync - this error means the transport layer is down, so let the scheduler retry on the next cycle instead of retrying inline
  4. If only ReadTimeouts, the statement may be genuinely huge; shrink the Flex Query date range

Example fix

# before
flex.download_statement rescue nil # silent drop

# after
begin
  flex.download_statement
rescue Provider::IbkrFlex::ApiError => e
  SyncFailure.record!(account, provider: :ibkr_flex, reason: e.message, retry_at: 1.hour.from_now)
end
Defensive patterns

Strategy: retry

Validate before calling

# cheap pre-flight before a sync run (fail fast instead of ~14s of provider backoff)
require "socket"
def ibkr_reachable?(timeout: 3)
  TCPSocket.new("ndcdyn.interactivebrokers.com", 443, connect_timeout: timeout).close
  true
rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, IO::TimeoutError
  false
end

Type guard

def ibkr_network_exhausted?(error)
  error.is_a?(Provider::IbkrFlex::ApiError) && error.message.start_with?("Network error after")
end

Try / catch

begin
  flex.download_statement
rescue Provider::IbkrFlex::ApiError => e
  raise unless e.message.start_with?("Network error after")
  SyncFailure.record!(account, provider: :ibkr_flex,
    reason: e.message, retry_at: 1.hour.from_now) # transport down; try next cycle
end

Prevention

When it happens

Trigger: DNS cannot resolve ndcdyn.interactivebrokers.com; egress firewall/proxy blocks HTTPS to IBKR; connection resets from an intermediary (SSL inspection); a single GetStatement hanging past the 120s HTTParty timeout three times in a row; local network outage during a sync job.

Common situations: Corporate/容器ized environments without outbound allowlist entries for IBKR, transient ISP or IBKR edge failures lasting longer than ~14s of cumulative backoff, runs scheduled during IBKR maintenance windows.

Related errors


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