Say, like a good citizen of the Ruby world, you’re using rbenv or the like to manage your application’s ruby version. You also write down your deps in a nice Gemfile, locked down in a Gemfile.lock. Then one day, you clone an old ruby repo and proceed to run bundle install only to find this message: Bundler::LockfileError: You must use Bundler 2 or greater with this lockfile..

Now of course, you know what is happening. This is an ancient project, still on ruby 2.3.0. What is odd though, that someone has decided to use a “modern” bundler for this project. Using ruby 2.3 with Bundler 2 is an anachronistic juxtaposition. One that’s odd, but might still work. Maybe the author had their reasons to not yet upgrade beyond 2.3, maybe it has just not been prioritized yet.

But for whatever reason, you now need Bundler 2 in your ruby 2.3 installation. What do you do? You install it, of course!

$ gem install bundler -v '2.0'

Nice, all that nice output confirms that bundler version 2.0 is now successfully installed. You then proceed to run bundle install. But to your surprise, it’s the same message again:

Bundler::LockfileError: You must use Bundler 2 or greater with this lockfile.`.

You think maybe you made a mistake when installing bundler. You retry the installation and it seems to complete successfully. However, checking bundle --version still returns 1.9 or something. You look around and find you can list all installed versions of a gem with the list subcommand. This uncovers some more information:

$ gem list bundler

*** LOCAL GEMS ***

bundler (2.0.1, 1.9.0)

Multiple gem Versions

Hrrm. So we definitely have multiple gem versions installed. Only question that remains is how to invoke the specific version we want? You search the internet, but hardly find a mention on how to resolve this except for this stackoverflow answer. You realize that it’s some wonky syntax, but running bundle _1.9_ install works! Also peculiar is that there’s no other mention of this on the internet except for this one tiny answer.

You exclaim: “Ruby is magic!” and proceed go your own way. Maybe you’ll even abandon ruby for node, without all this gem and bundler nonsense.

What’s Really Happening

If one was to dig further, one would find authoritative documentation on this, buried inside the command reference for the gem install subcommand:

The install command installs local or remote gem into a gem repository. For gems with executables ruby installs a wrapper file into the executable directory by default. This can be overridden with the –no-wrappers option. The wrapper allows you to choose among alternate gem versions using version.

For example rake _0.7.3_ --version will run rake version 0.7.3 if a newer version is also installed.

The last two paragraphs clarify everything! Essentially, all gems that contain executables are not called directly. Instead, gem installs a wrapper script which will look for this special argument and will invoke the requested version of the gem if it is specified.

This is particularly useful, when two different projects have the same ruby version specified in their .ruby-version files but use different versions of the command line tools like rake, rspec or rubocop. Even then, in most cases you won’t have a problem because prefixing bundle exec will ensure only the version specified in the Gemfile is the one that is present on the load path. So only when executing bundler itself or a gem that is installed “globally” are you likely to see this error.

Of course, in this day and age, providing a Dockerfile is a far saner solution. You want your devs to spend more time writing code and less time debugging random version conflicts.