{"record":{"id":"1151de401c07323e","repo":"we-promise/sure","slug":"network-error-after-max-retries-retries-e-me-1151de","errorCode":null,"errorMessage":"Network error after #{max_retries} retries: #{e.message}","messagePattern":"Network error after #(.+?) retries: #(.+?)","errorType":"exception","errorClass":"Provider::Snaptrade::ApiError","httpStatus":null,"severity":"error","filePath":"app/models/provider/snaptrade.rb","lineNumber":175,"sourceCode":"        begin\n          yield\n        rescue Faraday::TimeoutError, Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::ETIMEDOUT => e\n          retries += 1\n\n          if retries <= max_retries\n            delay = calculate_retry_delay(retries)\n            Rails.logger.warn(\n              \"SnapTrade OAuth: #{operation_name} failed (attempt #{retries}/#{max_retries}): \" \\\n              \"#{e.class}: #{e.message}. Retrying in #{delay}s...\"\n            )\n            sleep(delay)\n            retry\n          else\n            Rails.logger.error(\n              \"SnapTrade OAuth: #{operation_name} failed after #{max_retries} retries: \" \\\n              \"#{e.class}: #{e.message}\"\n            )\n            raise ApiError.new(\"Network error after #{max_retries} retries: #{e.message}\")\n          end\n        end\n      end\n\n      # For requests that must not be replayed (single-use codes, token rotation):\n      # translate a network failure into an ApiError without retrying.\n      def without_retry(operation_name)\n        yield\n      rescue Faraday::TimeoutError, Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::ETIMEDOUT => e\n        Rails.logger.error(\"SnapTrade OAuth: #{operation_name} failed (not retried, non-idempotent): #{e.class}: #{e.message}\")\n        raise ApiError.new(\"Network error (not retried, non-idempotent request): #{e.message}\")\n      end\n\n      def calculate_retry_delay(retry_count)\n        base_delay = INITIAL_RETRY_DELAY * (2 ** (retry_count - 1))\n        jitter = base_delay * rand * 0.25\n        [ base_delay + jitter, MAX_RETRY_DELAY ].min\n      end","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/snaptrade.rb#L157-L193","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  oauth_client.token_request(...)\nrescue Provider::Snaptrade::ApiError => e\n  retry_later_with_backoff if e.status_code.nil? # transport failure, not an HTTP error\n  raise\nend","preventionTips":["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"],"tags":["snaptrade","oauth","network","retries-exhausted","faraday"],"backgroundTag":"network-retries-exhausted","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}