What this usually means
Gem conflicts occur when Bundler's resolver cannot find a set of gem versions that satisfies all version constraints in your Gemfile and the transitive dependencies. This is not just a 'wrong version' problem—it's a constraint satisfaction failure. The resolver walks the dependency tree and if two branches require different, incompatible versions of the same gem (e.g., '~> 2.0' and '~> 3.0'), it fails. The error message usually lists the conflicting gems and the requirements, but the real cause is often a gem that pins an unexpectedly strict upper bound, or a Gemfile.lock that's been manually edited or corrupted.
The first ten minutes — establish facts before touching code.
- 1Run `bundle doctor` to check for common issues like missing dependencies or bad paths.
- 2Check the exact error line: look for 'Could not find compatible versions for gem X' and note the requirements from both sides.
- 3Run `bundle update <offending_gem>` to see if a newer version resolves the conflict.
- 4Temporarily comment out the newly added gem and run `bundle install` to confirm it's the trigger.
- 5Use `bundle viz` (requires ruby-graphviz) to visualize the dependency tree and spot the conflict.
- 6Check your Gemfile for any explicit version pins that might be too restrictive.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchGemfile — look for version constraints like '~> 2.0', '>= 1.0', or exact versions.
- searchGemfile.lock — inspect the versions of the conflicting gem and its dependents.
- searchbundle doctor output — reveals missing gems or platform issues.
- searchGemfile.lock's dependency graph: grep for the conflicting gem name to see its requirements.
- searchGemfile.lock's GEM section: each gem's 'requirements' field shows what it needs.
- searchConsole output from bundle install — the full tree printed before the error is key.
- searchGem spec files in vendor/bundle or ~/.bundle — check for corrupted downloads.
Practical causes, not theory. These are the things you will actually find.
- warningA gem in Gemfile has an overly strict version constraint (e.g., '= 2.0.1' when it should be '~> 2.0').
- warningTransitive dependency introduced by a new gem requires a version that conflicts with an existing pinned gem.
- warningGemfile.lock is out of sync after manual edits or merging branches with different lock files.
- warningPlatform-specific gems (e.g., nokogiri) cause conflicts when bundler resolves for a different platform.
- warningA gem was yanked from RubyGems, leaving the lock file referencing a version that no longer exists.
- warningRuby version mismatch: some gems require a newer Ruby than what's installed, causing resolver to fail.
Concrete fix directions. Pick the one that matches your root cause.
- buildRelax version constraints in Gemfile: change `gem 'foo', '~> 2.0'` to `gem 'foo', '>= 2.0'` (but be careful).
- buildRun `bundle update <conflicting_gem>` to let Bundler find a compatible set (this changes Gemfile.lock).
- buildDelete Gemfile.lock and run `bundle install` (last resort — loses locked transitive dependencies).
- buildAdd explicit overrides: `gem 'shared_dep', '~> 3.0'` in Gemfile to force a version that satisfies both.
- buildUse `bundle lock --update <gem>` to update only the lock for a specific gem.
- buildSplit Gemfile groups to isolate conflicting gems (e.g., `group :development`).
A fix you cannot prove is a guess. Close the loop.
- verifiedRun `bundle install` without errors.
- verifiedRun `bundle check` to confirm all dependencies are satisfied.
- verifiedStart the application (e.g., `rails s`) and verify no load errors.
- verifiedRun the test suite to catch any runtime load failures.
- verifiedRun `bundle list` to see the resolved versions and confirm no unexpected changes.
- verifiedCompare Gemfile.lock before and after (git diff) to ensure only intended changes were made.
Things that make this bug worse or harder to find.
- warningDeleting Gemfile.lock without understanding the consequences — you lose all transitive version locks.
- warningAdding version pins blindly without checking the gem's dependency tree.
- warningRunning `bundle update` without arguments — this updates everything and can introduce new conflicts.
- warningIgnoring the error message and just trying random gem version bumps.
- warningAssuming the conflict is always in your Gemfile — it's often a transitive dependency.
- warningUsing `bundle update` in production without testing in CI first.
The nokogiri Lockfile That Broke CI
Timeline
- 09:15Opened PR adding 'puma_worker_killer' gem to Gemfile.
- 09:17CI build fails: 'Bundler could not find compatible versions for gem 'nokogiri''
- 09:20Checked error: nokogiri >= 1.10 required by puma_worker_killer, but Gemfile.lock had 1.8.5
- 09:25Ran 'bundle update nokogiri' locally — installed 1.11.1 but broke other gems depending on 1.8.5 API
- 09:30Realized Gemfile had 'nokogiri', '~> 1.8.5' — too restrictive
- 09:35Changed to 'nokogiri', '~> 1.10' and ran bundle update
- 09:40Resolved all conflicts, committed Gemfile.lock update
- 09:45CI passes green
- 10:00PR merged; production deploy successful
I was adding puma_worker_killer to help manage memory in our Rails app. The PR was small: just add the gem, run bundle install, and commit. But CircleCI turned red with a nokogiri version conflict. The error said puma_worker_killer required nokogiri >= 1.10, but our Gemfile.lock had 1.8.5. I thought 'easy fix, just update nokogiri'.
I ran bundle update nokogiri locally and it bumped to 1.11.1. But then our app crashed in development because another gem (sentry-raven) depended on a nokogiri feature that was deprecated in 1.10. I went down a rabbit hole trying to pin sentry-raven to an older version. That broke because sentry-raven required a newer Ruby.
The actual fix was simpler: our Gemfile had gem 'nokogiri', '~> 1.8.5' — a pin from a security fix two years ago. I changed it to '~> 1.10' and ran bundle update nokogiri again. That satisfied puma_worker_killer and sentry-raven worked fine with 1.10.10. The lesson: don't keep overly restrictive pins from years ago. Review your Gemfile constraints regularly.
Root cause
Gemfile had an overly restrictive version pin on nokogiri (~> 1.8.5) that prevented Bundler from resolving a newer transitive dependency required by puma_worker_killer.
The fix
Relaxed the nokogiri constraint to '~> 1.10' and ran `bundle update nokogiri` to update the lock file.
The lesson
Version pins in Gemfile should be as broad as possible while still meeting security/API requirements. Regularly audit pins and remove outdated ones.
Bundler uses a backtracking dependency resolver (Molinillo) that tries to find a set of gem versions satisfying all constraints. When it fails, it prints the last conflict it detected. This isn't necessarily the only conflict, but it's the one that made the resolver give up.
The error message shows two (or more) requirements for the same gem, e.g., 'nokogiri (>= 1.10) required by puma_worker_killer' and 'nokogiri (= 1.8.5) required by Gemfile'. The resolver cannot satisfy both. The fix is to change one of the requirements. The 'Gemfile' requirement often comes from an explicit pin or a transitive dependency that was locked.
When a conflict occurs, Bundler prints a tree showing which gems depend on which. The format is like: 'rails (5.2.0) was resolved to 5.2.0, which depends on...'. Read from bottom to top: the last gem listed is the one that triggered the conflict.
Look for lines like 'Could not find compatible versions for gem X' and then scan the tree for 'X' in the dependency chains. The two chains that converge on X are your suspects. In the sample incident, puma_worker_killer required nokogiri >= 1.10, while the Gemfile.lock had nokogiri 1.8.5 from the pin.
`bundle doctor` checks for common problems: missing gems, bad paths, and platform mismatches. It doesn't solve conflicts, but it can highlight if a gem isn't installed for the current Ruby version.
`bundle viz` generates a dependency graph as a PNG (requires ruby-graphviz and graphviz system package). Save the image and look for the conflicting gem's node — it will have multiple incoming edges with different version constraints. This visual can quickly reveal which gems are causing the conflict.
Sometimes conflicts aren't about versions but platforms. For example, nokogiri has native extensions and different platform gems (x86_64-linux, x86_64-darwin). If your Gemfile.lock was generated on macOS and you deploy to Linux, Bundler may fail to find the right native gem.
Fix by running `bundle lock --add-platform x86_64-linux` to add the target platform, then re-bundle. Check the 'PLATFORMS' section in Gemfile.lock. Also ensure your Ruby version is compatible: run `ruby --version` and compare with gems' required_ruby_version in their gemspec.
In CI, always run `bundle install --deployment` (or `--path vendor/bundle`) to install gems into a consistent path. Ensure the CI environment has the same Ruby version and Bundler version as local. Use `bundler-cache` in CircleCI or `actions/cache` in GitHub Actions to cache the bundle.
If a conflict appears only in CI, check for platform differences: CI might use a different architecture (e.g., arm64) or a newer Ruby patch version. Add the CI platform to your Gemfile.lock explicitly.
Frequently asked questions
What does 'Bundler could not find compatible versions for gem X' mean exactly?
It means that the dependency resolver found at least two different requirements for gem X that cannot be satisfied simultaneously. For example, gem A requires X >= 2.0, but gem B requires X < 2.0. The resolver cannot choose a version of X that satisfies both, so it fails.
Should I delete Gemfile.lock to fix a conflict?
Only as a last resort. Deleting Gemfile.lock forces Bundler to resolve all dependencies from scratch, which may solve the conflict but also changes versions of other gems, potentially introducing new bugs. Better to run `bundle update <conflicting_gem>` or relax constraints.
How do I find out which gem introduced a conflicting transitive dependency?
Look at the error output: it lists the dependency tree. The conflicting gem appears at the end of two chains. Read upwards to see which top-level gems depend on the conflicting gem. You can also run `gem dependency <gem_name>` on the command line (outside Bundler) to see its dependencies.
Why does bundle install work locally but fail in CI?
Common reasons: different Ruby version, different platform (e.g., macOS vs Linux), or a stale cache. Ensure CI uses the same Ruby version and that you commit Gemfile.lock. Also check if CI is using a different Bundler version. Run `bundle lock --add-platform` to add the CI platform.
What's the difference between '~> 2.0' and '>= 2.0'?
'~> 2.0' means >= 2.0 and < 3.0 (pessimistic version constraint). '>= 2.0' means any version >= 2.0, which may include major version bumps. Using '~>' is safer because it prevents unexpected breaking changes from a new major version. Conflicts often arise when two gems use different pessimistic constraints that don't overlap.