teamcapybara/capybara · critical · LoadError

Capybara requires `puma` version 3.8.0 or higher, please upg

Error message

Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block

What it means

Puma is installed but too old: Capybara's :puma server block builds and runs the Puma server itself (to avoid signal-handler side effects of the rack handler's #run), which requires the handler to expose a .config class method. Rack::Handler::Puma gained config in puma 3.8.0; older versions fail the respond_to?(:config) check and raise LoadError.

Source

Thrown at lib/capybara/registrations/servers.rb:34

  end
  options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
  base_class::Handler::WEBrick.run(app, **options)
end

Capybara.register_server :puma do |app, port, host, **options| # rubocop:disable Metrics/BlockLength
  begin
    require 'rackup'
  rescue LoadError # rubocop:disable Lint/SuppressedException
  end
  begin
    require 'rack/handler/puma'
  rescue LoadError
    raise LoadError, 'Capybara is unable to load `puma` for its server, please add `puma` to your project or specify a different server via something like `Capybara.server = :webrick`.'
  end
  puma_rack_handler = defined?(Rackup::Handler::Puma) ? Rackup::Handler::Puma : Rack::Handler::Puma

  unless puma_rack_handler.respond_to?(:config)
    raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
  end

  # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
  # Therefore construct and run the Server instance ourselves.
  # puma_rack_handler.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
  default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
  options = default_options.merge(options)

  conf = puma_rack_handler.config(app, options)
  conf.clamp

  puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
  require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)

  logger = (defined?(Puma::LogWriter) ? Puma::LogWriter : Puma::Events).then do |cls|
    conf.options[:Silent] ? cls.strings : cls.stdio
  end
  conf.options[:log_writer] = logger

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Upgrade puma to at least 3.8.0 (puma ~> 5.0 or ~> 6.0 recommended) and run bundle update puma.
  2. Check what is pinning it: bundle info puma and look at Gemfile.lock dependencies that constrain the version.
  3. If an upgrade is impossible, use a different server: Capybara.server = :webrick (add the webrick gem on Ruby 3+).
  4. Confirm the runtime version Capybara sees: require 'puma'; puts Puma::Const::PUMA_VERSION in the test boot.

Example fix

# before (Gemfile)
gem 'puma', '~> 2.16'

# after (Gemfile)
gem 'puma', '~> 6.0'
Defensive patterns

Strategy: validation

Validate before calling

require 'puma'
raise LoadError, 'Capybara needs puma >= 3.8.0' if Gem::Version.new(Puma::Const::PUMA_VERSION) < Gem::Version.new('3.8.0')

Type guard

def puma_config_capable?
  handler = defined?(Rackup::Handler::Puma) ? Rackup::Handler::Puma : Rack::Handler::Puma
  handler.respond_to?(:config)
end

Prevention

When it happens

Trigger: Gemfile.lock pins puma to < 3.8.0 (e.g. puma (~> 2.16) on a legacy Rails app) or a system/global old puma shadows the bundled one; the first spec that boots the Capybara server raises this.

Common situations: Legacy applications with old puma constraints; transitive gem pins preventing an upgrade; deploying test suite into an image that ships its own puma; upgrading Capybara without revisiting the puma constraint.

Related errors


AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21). Data as JSON: /api/errors/2b19d932ab5ebf46. Report an issue: GitHub.