we-promise/sure · error · Provider::Questrade::Error
network_error
network_error
Error message
Network error after #{max_retries} retries: #{e.message} What it means
Raised by Provider::Questrade's with_retries: the operation kept raising one of RETRYABLE_ERRORS (SocketError, Net::OpenTimeout, Net::ReadTimeout, ECONNRESET, ECONNREFUSED, ETIMEDOUT, EOFError) through MAX_RETRIES=3 attempts with exponential backoff plus jitter (2s, ~4s, ~8s, capped at 30s), after which Error(:network_error) replaces the last exception. The preceding 'Questrade API: ... failed after 3 retries' log line records the underlying class and message.
Source
Thrown at app/models/provider/questrade.rb:221
begin
yield
rescue *RETRYABLE_ERRORS, RetryableResponseError => e
retries += 1
if retries <= max_retries
delay = calculate_retry_delay(retries)
Rails.logger.warn(
"Questrade API: #{operation_name} failed (attempt #{retries}/#{max_retries}): " \
"#{e.class}: #{e.message}. Retrying in #{delay}s..."
)
sleep(delay)
retry
else
Rails.logger.error(
"Questrade 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
# Record provider failure detail to the super-admin /settings/debug log
# (the sanctioned channel) instead of echoing the raw response body into
# application logs or exception messages, where the importer re-logs it.
def capture_response_error(reason, response)
DebugLogEntry.capture(
category: "provider_sync",
level: "error",
message: "Questrade API #{reason} (#{response.code})",View on GitHub (pinned to e69894adb9)
Solutions
- Check the logged underlying exception class first — ECONNREFUSED vs SocketError vs ECONNRESET point to firewall vs DNS vs proxy respectively.
- Verify outbound connectivity from the runtime host: curl -v https://login.questrade.com and curl -v <api_server>/v1/accounts.
- If DNS (SocketError), fix resolver config in the container/cluster.
- If resets are proxy-induced, configure the proxy or TLS settings for the HTTParty client.
- If genuinely transient but longer than the window, schedule a later retry of the whole sync instead of widening MAX_RETRIES inline.
Example fix
# before result = provider.get_balances(account_id: id) # after begin result = provider.get_balances(account_id: id) rescue Provider::Questrade::Error => e raise if e.error_type != :network_error SyncJob.perform_in(15.minutes, item.id) # defer; don't hammer a dead network end
Defensive patterns
Strategy: retry
Try / catch
begin provider.get_balances(account_id: id) rescue Provider::Questrade::Error => e raise if e.error_type != :network_error SyncJob.perform_in(15.minutes, item.id) # internal retries already exhausted end
Prevention
- Verify outbound DNS/firewall from the deploy environment before first Questrade sync.
- Let the job system reschedule rather than widening in-process retries past the SDK's 3 attempts.
- Monitor which socket class actually failed (logged pre-raise) to distinguish env problems from incidents.
When it happens
Trigger: DNS failure resolving login.questrade.com or the per-session api_server host; firewall/security-group blocking outbound HTTPS; Questrade's api_server host unreachable from your region; connection resets on every attempt (some proxies kill the long-lived 120s-timeout requests); local dev machine offline.
Common situations: Deployed app missing outbound internet rules (ECONNREFUSED/ETIMEDOUT); corporate proxy MITM resetting TLS to Questrade; DNS misconfiguration in the container (SocketError); transient ISP-level outage lasting longer than the ~14s retry window.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/71c7666578edd6e9.
Report an issue: GitHub.