lostisland/faraday · error · ArgumentError
bad argument (expected URI object or URI string)
Error message
bad argument (expected URI object or URI string)
What it means
Faraday::Utils.URI normalizes URL inputs for internal use: an object responding to #host is passed through untouched (any URI-like value), an object responding to #to_str is handed to the configured URI parser (default_uri_parser, Kernel.URI unless overridden), and everything else raises ArgumentError. nil is included in 'everything else', so a nil or non-stringy URL reaching this helper is the failure.
Source
Thrown at lib/faraday/utils.rb:76
"Basic #{value}"
end
class << self
attr_writer :default_params_encoder
end
# Normalize URI() behavior across Ruby versions
#
# url - A String or URI.
#
# Returns a parsed URI.
def URI(url) # rubocop:disable Naming/MethodName
if url.respond_to?(:host)
url
elsif url.respond_to?(:to_str)
default_uri_parser.call(url)
else
raise ArgumentError, 'bad argument (expected URI object or URI string)'
end
end
def default_uri_parser
@default_uri_parser ||= Kernel.method(:URI)
end
def default_uri_parser=(parser)
@default_uri_parser = if parser.respond_to?(:call) || parser.nil?
parser
else
parser.method(:parse)
end
end
# Receives a String or URI and returns just
# the path with the query string sorted.
def normalize_path(url)View on GitHub (pinned to b25b1b26cc)
Solutions
- Coerce to a String or a real URI before passing: URI.parse(str) or str.to_s.
- Nil-guard configuration at load time and fail loudly with a clear message: raise "base_url missing" if base_url.nil?, or supply a default.
- For custom URI wrapper classes, implement #host (marker of URI-ness) or #to_str so Faraday can use them.
- If you configured Faraday.default_uri_parser, verify it still returns URI objects — custom parsers changing the contract also surface here.
Example fix
# before
base_url = ENV['API_URL'] # nil when unset
conn = Faraday.new(base_url)
# later request => ArgumentError: bad argument (expected URI object or URI string)
# after
base_url = ENV.fetch('API_URL') { 'https://api.example.com' }
conn = Faraday.new(URI.parse(base_url)) Defensive patterns
Strategy: type-guard
Validate before calling
url = url.to_s
url = ENV.fetch('API_URL') { 'https://api.example.com' } if url.empty?
conn = Faraday.new(URI.parse(url)) Type guard
def uri_like?(url) url.respond_to?(:host) || url.respond_to?(:to_str) end
Try / catch
begin
Faraday.new(url)
rescue ArgumentError => e
raise unless e.message.include?('expected URI object')
Faraday.new(url.to_s) # coerce and retry once
end Prevention
- Validate URL config at boot with URI.parse and a presence check; fail fast with a named error, not mid-request.
- Use ENV.fetch with a default for URL config instead of ENV[...] which yields nil silently.
- Custom URI wrapper classes must respond to #host or #to_str to pass Faraday's normalization.
When it happens
Trigger: Passing nil, a Symbol, Integer, Hash, or Pathname where a URL is required — e.g. Faraday.new(config.base_url) when config.base_url is nil-from-missing-env-var in some paths, conn.url_prefix = 42, or middleware assigning a custom wrapper object to env[:url] that implements neither #host nor #to_str.
Common situations: YAML/env configuration where the URL key is misspelled so the value is nil only in certain environments; passing an options Hash instead of a URL string; custom URI value objects that lack #host; Addressable::URI works (responds to #host) but a Struct wrapping a URI does not.
Related errors
- Expected :read, :write, :open. Got #{type.inspect} :(
- unknown http method: #{method}
- #memoized must be called with a block
- An attempt to run a request with a Faraday::Connection witho
- Unexpected params received (got #{params.size} instead of 1)
AI-assisted analysis of lostisland/faraday@b25b1b26cc (2026-08-21).
Data as JSON: /api/errors/80bf6fc1f48e412e.
Report an issue: GitHub.