skip to content

How do rbenv shims route a ruby or bundle call to the right Ruby version, and when must you run rbenv rehash?

level: middleimportance: must knowfreq 55%

answer

  1. one directory first on PATH
  2. every shim runs rbenv exec
  3. version bin dir prepended to PATH
  4. rehash scans versions/*/bin
  5. automatic after install and gem install

basics

~10 s

rbenv puts ~/.rbenv/shims first on PATH; each shim is a small script that runs rbenv exec, which resolves the selected version and execs that version's executable. rbenv rehash regenerates shims when new executables appear.

solid answer

~40 s

`rbenv init -` prepends `~/.rbenv/shims` to `PATH`. Each shim is a copy of one tiny bash script that calls `rbenv exec <name>`; `rbenv exec` resolves the version (`RBENV_VERSION`, `.ruby-version`, `~/.rbenv/version`), finds `versions/<v>/bin/<name>`, prepends that `bin` directory to `PATH`, exports `RBENV_VERSION` and execs the real binary. Because shims hold no version, switching versions needs no rehash. `rbenv rehash` is needed when a **new executable name** appears: it rebuilds shims from every `versions/*/bin`. It runs automatically after `rbenv install` and, through a RubyGems plugin rbenv ships, after `gem install` or `bundle install` adds executables; I run it by hand after dropping a hand-built Ruby into `~/.rbenv/versions`.

code

bash · 8 lines
bash
gem install rackup           # RubyGems plugin triggers rbenv rehash
rbenv which rackup           # ~/.rbenv/versions/4.0.7/bin/rackup
rbenv whence rackup          # versions that provide rackup
# a Ruby compiled by hand is not seen until shims are rebuilt
ln -s /opt/ruby-4.0.7-debug ~/.rbenv/versions/4.0.7-debug
rbenv rehash
RBENV_VERSION=3.4.7 rbenv which rackup
# rbenv: rackup: command not found (exit 127) if 3.4.7 lacks it

go deeper

for a junior

Remember that shims sit first on PATH and forward each call, and that a new gem command that says not found usually just needs rbenv rehash.

for a middle

Walk through shim, rbenv exec, rbenv which and the PATH prepend, and say exactly which events rehash automatically and which do not.

for a senior

Diagnose shim problems with rbenv which, whence and RBENV_DEBUG, and explain how the exported RBENV_VERSION pins child processes to the parent's Ruby.

for a principal

Weigh resolving the version at every call, which costs a little startup time, against rewriting PATH on directory change, which costs shell intrusion and hidden state.

## The problem shims solve A machine with several Rubies has several `ruby`, `gem`, `irb` and `bundle` executables, one set per version, and the shell simply runs the first match it finds on `PATH`. rbenv does not rewrite `PATH` every time you change directory. Instead it puts **one** directory in front of everything else, `~/.rbenv/shims`, and fills it with small scripts called **shims**, one per executable name. The shell finds the shim first; the shim then decides, at the moment of the call, which real executable to run. `rbenv init -` (the line your shell startup file evaluates) is what prepends `~/.rbenv/shims` to `PATH`. The README calls that step "basically the only requirement for rbenv to function properly". ## What a shim actually is Every shim is a copy of one **prototype shim**, a short bash script. When you run `bundle install`: 1. The shell finds `~/.rbenv/shims/bundle` first on `PATH`. 2. The shim exports `RBENV_ROOT` and runs `rbenv exec bundle install`, passing all arguments through. 3. For the `ruby` shim only, if an argument is a path to an existing file, the shim sets `RBENV_DIR` to that file's directory, so `.ruby-version` lookup starts next to the script rather than in the current directory. A shim contains no Ruby and no version number. That is why switching versions never requires regenerating shims. ## rbenv exec, step by step `rbenv exec <command>` is where the decision happens: 1. It resolves the version name (`RBENV_VERSION`, else the nearest `.ruby-version`, else `~/.rbenv/version`, else `system`). 2. It asks `rbenv which <command>` for the full path, normally `~/.rbenv/versions/<version>/bin/<command>`. 3. It **exports `RBENV_VERSION`** with the resolved name and, unless the version is `system`, **prepends that version's `bin` directory to `PATH`**. 4. It `exec`s the real executable, replacing itself. Step 3 matters for child processes: a gem executable that starts `ruby`, or a script that shells out, finds the same version's `bin` first and inherits `RBENV_VERSION`, so it stays on the parent's Ruby even if it changes directory. If the command exists in some installed version but not the active one, `rbenv which` fails with `rbenv: rackup: command not found`, lists the versions that do have it, and exits with status 127. Shims are the **union** of executable names across all versions, so a shim can exist for a command the current version lacks. ## What rehash does `rbenv rehash` regenerates the shims directory: - It creates the prototype shim `~/.rbenv/shims/.rbenv-shim`; creating that file with `noclobber` doubles as a **lock**, so a second concurrent rehash fails with `cannot rehash: ... .rbenv-shim exists`. - If existing shims differ from the new prototype (rbenv was upgraded), it deletes them all. - It collects the basename of every executable in `~/.rbenv/versions/*/bin`, plus `$GEM_HOME/bin` when that variable is set, and lets plugins register more through `rehash` hooks. - It copies the prototype to every missing name and deletes shims whose name no version provides any more. In a shell with rbenv's integration, `rbenv rehash` also runs `hash -r` so the shell forgets cached command locations (fish excepted). ## When rehash runs by itself, and when you run it | Event | Rehash automatic? | |---|---| | `rbenv install` (ruby-build) finishes successfully | Yes, ruby-build calls it | | `gem install` of a gem with executables into the default or user bin dir | Yes, via the RubyGems plugin rbenv loads through `rbenv exec` | | `bundle install` that adds executables to a bin dir inside the default gem path | Yes, the same plugin wraps Bundler's installer | | Copying or symlinking a hand-built Ruby into `~/.rbenv/versions` | No, run `rbenv rehash` | | `rbenv local` / `global` / `shell` switching versions | Not needed at all | | Shell startup | Only if the init line omits `--no-rehash`; the line `rbenv init` 1.3 writes includes it | ## Symptoms and checks - A freshly installed gem's command says `command not found`: run `rbenv rehash`, then check with `rbenv which <cmd>`. - `rbenv whence <cmd>` lists which installed versions provide a command. - `RBENV_DEBUG=1` (or `rbenv --debug <subcommand>`) traces the scripts line by line. - A stale `.rbenv-shim` left by a killed rehash blocks every later one; delete it only when no rehash is running. - After upgrading rbenv, the first rehash sees that the prototype changed and replaces every shim; that is expected, not corruption. - The set of shims depends on the installed versions (and `GEM_HOME/bin` when set), not on which version is active, so rehashing under one version serves all of them.

  • A shim exists for rackup, but the active Ruby version never installed that gem; what happens when you run rackup?
    The shim runs, `rbenv exec` asks `rbenv which rackup`, and no executable exists under the active version's `bin`. rbenv prints `rbenv: rackup: command not found`, lists the installed versions that do provide it, and exits with status 127. Shims are the union of executable names across every installed version, not a list for the current one.
  • Why does rbenv exec export RBENV_VERSION and prepend the version's bin directory to PATH?
    So everything the command starts stays on the same Ruby. A gem executable that runs `ruby`, or a script shelling out to `gem`, finds `versions/<v>/bin` first and inherits `RBENV_VERSION`, which outranks any `.ruby-version`. The flip side: a child process that changes into another project still runs the parent's version.
  • rbenv rehash fails with 'cannot rehash: ~/.rbenv/shims/.rbenv-shim exists'; why, and what do you do?
    Rehash writes the prototype shim with `noclobber`, and that file doubles as a lock. Either another rehash is running right now (a parallel gem install, a shell starting up), or an earlier rehash was killed before its cleanup trap removed the file. Wait and retry; if no rehash is running, delete `.rbenv-shim` and run `rbenv rehash` again.

A shim is a hotel front desk for one name: every call for ruby lands at the same desk, the clerk checks which guest (project) is calling and forwards it to the right room (version). Guests changing rooms need no new desk; a brand-new service name does, and rehash is the carpenter who builds it.

saying these in an interview costs you the question

  • Shims are symlinks to the global version's real ruby binary
  • You must run rbenv rehash after every rbenv local or version switch
  • rbenv rewrites PATH each time you cd into a project
  • rbenv rehash downloads or reinstalls missing gem executables
  • A shim is generated only for executables of the active version