we-promise/sure · error · Provider::Snaptrade::ApiError
Network error after #{max_retries} retries: #{e.message}
Error message
Network error after #{max_retries} retries: #{e.message} What it means
Raised by the OAuth module's with_retries in Provider::Snaptrade: retryable Faraday network errors (TimeoutError, ConnectionFailed, ECONNRESET, ETIMEDOUT) persisted past max_retries attempts, each with a fixed sleep(delay) between tries (delay from exponential backoff with jitter). The final failure is logged and re-raised as a plain ApiError with no status_code, distinguishing a transport failure from an HTTP error response.
Source
Thrown at app/models/provider/snaptrade.rb:175
begin
yield
rescue Faraday::TimeoutError, Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::ETIMEDOUT => e
retries += 1
if retries <= max_retries
delay = calculate_retry_delay(retries)
Rails.logger.warn(
"SnapTrade OAuth: #{operation_name} failed (attempt #{retries}/#{max_retries}): " \
"#{e.class}: #{e.message}. Retrying in #{delay}s..."
)
sleep(delay)
retry
else
Rails.logger.error(
"SnapTrade OAuth: #{operation_name} failed after #{max_retries} retries: " \
"#{e.class}: #{e.message}"
)
raise ApiError.new("Network error after #{max_retries} retries: #{e.message}")
end
end
end
# For requests that must not be replayed (single-use codes, token rotation):
# translate a network failure into an ApiError without retrying.
def without_retry(operation_name)
yield
rescue Faraday::TimeoutError, Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::ETIMEDOUT => e
Rails.logger.error("SnapTrade OAuth: #{operation_name} failed (not retried, non-idempotent): #{e.class}: #{e.message}")
raise ApiError.new("Network error (not retried, non-idempotent request): #{e.message}")
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
endView on GitHub (pinned to e69894adb9)
Solutions
- Verify reachability from the app host: curl -v https://api.snaptrade.com (any HTTP status proves transport works)
- Re-run the operation - transient connectivity blips are what the retry loop exists for; sustained failure means an environment problem
- Check the warn logs for the exception class - ConnectionFailed points to DNS/firewall, TimeoutError to a slow or unresponsive endpoint
- If timeouts are chronic, review the timeout values on the oauth_connection (30s/10s) and any egress proxy in front of the app
Defensive patterns
Strategy: retry
Try / catch
begin oauth_client.token_request(...) rescue Provider::Snaptrade::ApiError => e retry_later_with_backoff if e.status_code.nil? # transport failure, not an HTTP error raise end
Prevention
- Distinguish status_code.nil? (network) from status_code set (HTTP error) on ApiError
- Verify egress to api.snaptrade.com from every deploy environment
- Prefer scheduling a retry over surfacing transport errors to end users
When it happens
Trigger: Three-plus consecutive Faraday::TimeoutError/Faraday::ConnectionFailed while reaching api.snaptrade.com/oauth/* - DNS failure, blocked egress, or the endpoint not answering within the 30s connection timeout and 10s open timeout configured on the OAuth connection.
Common situations: Local network outage or VPN blocking api.snaptrade.com; SnapTrade API incident causing connect timeouts; CI environment without network access running OAuth code paths; slow proxy adding latency beyond the configured timeouts.
Related errors
- Network error (not retried, non-idempotent request): #{e.mes
- Lunchflow provider is not configured
- pagination_error
- request_failed
- Network error after #{max_retries} retries: #{e.message}
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/1151de401c07323e.
Report an issue: GitHub.