lostisland/faraday · error · ArgumentError

Expected :read, :write, :open. Got #{type.inspect} :(

Error message

Expected :read, :write, :open. Got #{type.inspect} :(

What it means

Faraday::Adapter#request_timeout resolves which timeout option to use by looking the type up in the frozen TIMEOUT_KEYS map ({ read: :read_timeout, open: :open_timeout, write: :write_timeout }), falling back to the generic :timeout option. The map only accepts the symbols :read, :write and :open; any other value raises ArgumentError. It is mostly an adapter-author-facing API: built-in adapters call it with hardcoded symbols, so end users usually hit it from custom adapters or direct calls.

Source

Thrown at lib/faraday/adapter.rb:95

      env.response.finish(env) unless env.parallel? || !finished
      env.response
    end

    # Fetches either a read, write, or open timeout setting. Defaults to the
    # :timeout value if a more specific one is not given.
    #
    # @param type [Symbol] Describes which timeout setting to get: :read,
    #                      :write, or :open.
    # @param options [Hash] Hash containing Symbol keys like :timeout,
    #                       :read_timeout, :write_timeout, or :open_timeout
    #
    # @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
    #                        has been set.
    def request_timeout(type, options)
      key = TIMEOUT_KEYS.fetch(type) do
        msg = "Expected :read, :write, :open. Got #{type.inspect} :("
        raise ArgumentError, msg
      end
      options[key] || options[:timeout]
    end
  end
end

require 'faraday/adapter/test'

View on GitHub (pinned to b25b1b26cc)

Solutions

  1. Pass one of the three supported symbols: :read, :write, or :open (request_timeout(:open, env[:request]) maps to options[:open_timeout] || options[:timeout]).
  2. If the type comes from configuration, normalize it before the call: type = type.to_s.downcase.to_sym and reject values outside the set.
  3. For timeout kinds with no Faraday mapping (e.g. a TLS handshake timeout), read the option directly from the options hash (options[:ssl_timeout]) instead of going through request_timeout.
  4. In custom adapter specs, enumerate the supported types so a renamed symbol fails in CI instead of in production.

Example fix

// before (custom adapter)
def call(env)
  timeout = request_timeout(:connect, env[:request])
  # => ArgumentError: Expected :read, :write, :open. Got :connect :(
end

// after
def call(env)
  timeout = request_timeout(:open, env[:request]) # maps to :open_timeout / :timeout
end
Defensive patterns

Strategy: validation

Validate before calling

return unless Faraday::Adapter::TIMEOUT_KEYS.key?(type)
timeout = adapter.request_timeout(type, options)

Type guard

def valid_timeout_type?(type)
  Faraday::Adapter::TIMEOUT_KEYS.key?(type)
end

Try / catch

begin
  adapter.request_timeout(type, options)
rescue ArgumentError => e
  logger.warn("unsupported timeout type #{type}: #{e.message}")
  options[:timeout] # fall back to the generic timeout
end

Prevention

When it happens

Trigger: Calling request_timeout with an unsupported symbol, e.g. request_timeout(:connect, options), request_timeout(:total, ...) or a string like 'read' instead of :read. Writing a custom adapter that copies timeout handling from another HTTP library and reuses its option names (:connect_timeout, :ssl_timeout). A typo such as :wirte or :opne.

Common situations: Authoring or maintaining a custom Faraday adapter (the main real-world source); refactoring timeout code and renaming the type symbols; passing a user-supplied configuration string straight through as the type argument instead of normalizing it to a symbol.

Related errors


AI-assisted analysis of lostisland/faraday@b25b1b26cc (2026-08-21). Data as JSON: /api/errors/1bf1bdbf163d7281. Report an issue: GitHub.