A Ruby script without Bundler fails on require "sinatra" with Gem::ConflictError: sinatra 4.2.1 cannot activate because an active rack 2.2 conflicts with rack (>= 3.0.0, < 4). What happened, and how do you fix it?
answer
- a dependency already active at the wrong version
- raise_if_conflicts during activation
- Gem::ConflictError < Gem::LoadError
- #conflicts and #target
- gem dependency sinatra -v 4.2.1
basics
~20 sSomething activated rack 2.2 earlier in the process, and sinatra 4.2.1 declares rack >= 3.0.0, < 4. Activation checks each runtime dependency against Gem.loaded_specs and raises Gem::ConflictError. Fix the earlier activation, or let Bundler resolve the whole set.
solid answer
~40 s`Gem::ConflictError` comes from `Specification#activate`: before activating sinatra 4.2.1, RubyGems checks each runtime dependency against `Gem.loaded_specs`, and rack is already active at 2.2, which fails sinatra's `rack (>= 3.0.0, < 4)`. Since one version per gem is allowed per process, it cannot swap rack. So the cause is **earlier**: a `gem "rack", "~> 2.2"` pin, or another gem that needs rack below 3 and was required first. `e.target` is sinatra and `e.conflicts` maps the rack spec to the unmet dependency. Fix it by removing or loosening that pin, reordering so sinatra activates first if the other gem accepts rack 3, or installing compatible versions; for an application, a Gemfile and lockfile resolve the whole set.
code
ruby · 10 linesgem "rack", "~> 2.2" # activates rack 2.2 first
begin
require "sinatra" # sinatra 4.2.1 needs rack >= 3.0.0, < 4
rescue Gem::ConflictError => e
puts e.target.full_name # sinatra-4.2.1
e.conflicts.each do |active, deps|
puts "#{active.full_name} fails #{deps.join(", ")}"
end
endgo deeper
Recall that a Ruby process uses one version of each gem, so two gems needing incompatible versions of a shared dependency cannot both load.
Explain the check activation performs against Gem.loaded_specs and how ConflictError differs from missing-gem and already-activated errors.
Trace the earlier activation that caused it, inspect target and conflicts, avoid rescuing it away, and move the script to a resolved lockfile.
Set the rule for when ad-hoc scripts must get a Gemfile, and how shared tooling images avoid order-dependent activation across teams.
## Where the error comes from In RubyGems, **activating** a gem means choosing one installed version, checking it against what the process already has, activating its runtime dependencies and adding its `lib` to the load path. When `require "sinatra"` finds sinatra 4.2.1, `Gem::Specification#activate` runs a conflict check first (`raise_if_conflicts`): 1. For each **runtime dependency** of sinatra, look up `Gem.loaded_specs[dep.name]`. 2. If that gem is already active and its version does **not** satisfy the dependency, record a conflict. 3. If any conflict exists, raise `Gem::ConflictError`. sinatra 4.2.1's gemspec declares `rack >= 3.0.0, < 4`. Rack 2.2 is active, so the check fails, and the message has the form `Unable to activate sinatra-4.2.1, because rack-2.2.<patch> conflicts with rack (>= 3.0.0, < 4)`. RubyGems cannot fix this by itself: rack 2.2's code is already loaded, and **one version per gem per process** is the rule. Swapping it would mean loading a second copy of the library over the first. ## The error's anatomy - `Gem::ConflictError` inherits from `Gem::LoadError`, which inherits from Ruby's `LoadError`, so a bare `rescue LoadError` swallows it. - `e.target` is the specification that could not be activated (sinatra 4.2.1). - `e.conflicts` is a Hash from the already-active spec (rack 2.2) to the list of dependencies it fails. It is one of several related errors, and telling them apart points straight at the cause: | Error | Meaning | |---|---| | `Gem::ConflictError` | a **dependency** of the gem being activated is active at an incompatible version | | `Gem::LoadError` "can't activate X, already activated Y" | the **same gem** was asked for at a second version | | `Gem::MissingSpecVersionError` | the gem is installed, but no installed version matches | | `Gem::MissingSpecError` | the gem is not installed in any `GEM_PATH` directory | ## Finding who activated rack 2.2 The conflict is a symptom; the cause ran earlier. Typical culprits: - an explicit `gem "rack", "~> 2.2"` or `gem "rack", "< 3"` near the top of the script or a helper it loads; - a `require` of another gem whose own dependency requires rack below 3, which activated rack 2.2 as a side effect. Useful probes: 1. `Gem.loaded_specs.keys` right before the failing require shows everything already active. 2. `gem dependency sinatra -v 4.2.1` prints sinatra's requirements. 3. `gem dependency rack -R` lists installed gems that depend on rack, and with what requirements. ## Why the order of requires matters Activation happens lazily, the first time a gem is needed, so the same set of installed gems can succeed or fail depending on **which require runs first**: 1. If sinatra is required first, RubyGems activates sinatra 4.2.1 and then activates the highest rack that satisfies `>= 3.0.0, < 4`, which is 3.2.7. A gem required later that also accepts rack 3 loads fine; one that needs rack below 3 now fails instead. 2. If the gem that needs rack below 3 is required first, rack 2.2 becomes active, and sinatra fails with the `Gem::ConflictError` in this question. This is why the error can appear after an innocent-looking reordering of `require` lines, or only in one entry point of a program that shares code with another. ## Fixing it - **Remove or loosen the pin** if nothing truly needs rack 2.2. - **Reorder** so sinatra is required first, if the other gem also accepts rack 3; then rack 3 is active and the other gem's check passes. - **Install compatible versions**: an older sinatra that accepts rack 2, or a newer version of the other gem that accepts rack 3. - **Hand the problem to a resolver.** Order-dependent activation is exactly what a Gemfile and `Gemfile.lock` remove: Bundler resolves every gem together before anything loads and activates exactly the locked set. Any script with more than a couple of dependencies belongs there. What not to do: rescue `Gem::LoadError` and continue. The process then runs without sinatra, or with a half-loaded dependency tree, and fails later somewhere less obvious.
- How does Gem::ConflictError differ from the "can't activate X, already activated Y" error?`Gem::ConflictError` is raised when a **dependency** of the gem being activated is already active at an incompatible version, as with sinatra needing rack 3 while rack 2.2 is active. "can't activate rack-3.2.7, already activated rack-2.2.x" is a plain `Gem::LoadError` raised when the **same gem** is requested at a second version, for example by a late `gem "rack", "3.2.7"`.
- Why can a rescue LoadError around a require hide this problem?Because `Gem::ConflictError` is a subclass of `Gem::LoadError`, which inherits from Ruby's `LoadError`. Code that rescues `LoadError` to make a library optional will treat a version conflict as "library not installed" and carry on without it, masking a real dependency clash that should have failed loudly.
saying these in an interview costs you the question
- Believes RubyGems can load rack 2.2 and rack 3 together
- Blames sinatra rather than whatever activated rack 2.2 earlier
- Rescues Gem::LoadError and continues without the gem
- Confuses Gem::ConflictError with the gem not being installed
- Thinks gem install sinatra alone fixes an activation-order conflict