we-promise/sure · error · LunchflowError
request_failed
request_failed
Error message
Exception during GET request: #{e.message} What it means
Raised by get_accounts (and the other GET wrappers) when the request blows up outside handle_response: SocketError / Net::OpenTimeout / Net::ReadTimeout (network layer, beyond the 120s HTTParty timeout), or - via the blanket `rescue => e` - ANY other exception, including LunchflowErrors that handle_response already raised for HTTP 4xx/5xx. Those get re-wrapped, so an expired x-api-key (401) surfaces with this same 'Exception during GET request' message and :request_failed, losing its original error_type. The only way to tell them apart is the preceding log line ('... failed:' = network vs 'Unexpected error during...' = re-wrapped HTTP error).
Source
Thrown at app/models/provider/lunchflow.rb:26
attr_reader :api_key, :base_url
def initialize(api_key, base_url: "https://lunchflow.app/api/v1")
@api_key = api_key
@base_url = base_url
end
# Get all accounts
# Returns: { accounts: [...], total: N }
def get_accounts
response = self.class.get(
"#{@base_url}/accounts",
headers: auth_headers
)
handle_response(response)
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "Lunch Flow API: GET /accounts failed: #{e.class}: #{e.message}"
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
rescue => e
Rails.logger.error "Lunch Flow API: Unexpected error during GET /accounts: #{e.class}: #{e.message}"
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
end
# Get transactions for a specific account
# Returns: { transactions: [...], total: N }
# Transaction structure: { id, accountId, amount, currency, date, merchant, description, isPending }
def get_account_transactions(account_id, start_date: nil, end_date: nil, include_pending: false)
query_params = {}
if start_date
query_params[:start_date] = start_date.to_date.to_s
end
if end_date
query_params[:end_date] = end_date.to_date.to_s
endView on GitHub (pinned to e69894adb9)
Solutions
- Check the Rails log to classify: 'GET /accounts failed: SocketError...' = genuine network issue; 'Unexpected error during GET /accounts: LunchflowError: Unauthorized' = re-wrapped HTTP error
- Verify api_key and base_url first - auth failures masquerade as this error
- Test connectivity: curl -H 'x-api-key: <key>' https://lunchflow.app/api/v1/accounts from the app host
- Fix the masking: re-raise LunchflowError untouched before the generic rescue (see exampleFix), then handle the real error_type
Example fix
# app/models/provider/lunchflow.rb - every GET wrapper
# before
handle_response(response)
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
rescue => e
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
end
# after
handle_response(response)
rescue LunchflowError
raise # keep HTTP error_type (:unauthorized etc.) intact
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
rescue => e
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
end Defensive patterns
Strategy: retry
Validate before calling
# cheap reachability pre-flight (also catches base_url typos in self-hosted setups) require "socket" def lunchflow_reachable?(base_url, timeout: 3) uri = URI.parse(base_url) TCPSocket.new(uri.host, uri.port || 443, connect_timeout: timeout).close true rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, IO::TimeoutError, URI::InvalidURIError false end
Type guard
def lunchflow_request_failed?(error)
error.is_a?(Provider::Lunchflow::LunchflowError) && error.error_type == :request_failed
end
def lunchflow_network_failure?(log_line)
log_line.include?("failed: SocketError") || log_line.include?("failed: Net::")
end Try / catch
# NOTE: until the rescue-wrap is fixed, check logs to separate network failures # from re-wrapped HTTP errors; both surface as :request_failed. begin provider.get_accounts rescue Provider::Lunchflow::LunchflowError => e raise unless e.error_type == :request_failed RetryableSync.schedule(account, wait: 5.minutes) # transient-first assumption end
Prevention
- Verify api_key and base_url first - expired keys currently masquerade as :request_failed because of the blanket rescue
- Add `rescue LunchflowError; raise` before the generic rescue in the provider to stop error_type loss
- Allowlist lunchflow.app (or your custom base_url host) on egress firewalls
- Smoke-test GET /accounts in CI to catch both auth and connectivity drift early
When it happens
Trigger: DNS/network failure reaching lunchflow.app (or a custom base_url); response slower than the 120s timeout; any HTTP error status from handle_response on GET /accounts, /accounts/{id}/transactions, /balance or /holdings being swallowed and re-raised as :request_failed by the broad rescue.
Common situations: Expired or wrong x-api-key appearing as a generic request failure, self-hosted/custom base_url typos, egress firewall blocking lunchflow.app, and developers debugging 'network' errors that were actually 401s.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/af7ece103b78118e.
Report an issue: GitHub.