skip to content

When you `cd` into a project, how does RVM choose the Ruby and gemset, and why must a `.rvmrc` be trusted first?

level: middleimportance: nice to knowfreq 14%

answer

  1. cd hook walks up the tree
  2. .rvmrc beats .ruby-version
  3. .rvmrc is sourced shell code
  4. trust stored with checksums
  5. rvm rvmrc to ruby-version

basics

~20 s

RVM's cd hook walks up from the new directory and loads the first project file it finds, with .rvmrc checked before .ruby-version. A .rvmrc is shell code sourced into your shell, so RVM asks before running it; .ruby-version is plain data.

solid answer

~40 s

RVM hooks directory changes: in bash it replaces `cd`, `pushd` and `popd` with functions, in zsh it adds a `chpwd` hook. After each change it walks from the new directory upward, checking `.rvmrc`, `.versions.conf`, `.ruby-version`, then a `Gemfile` with a `ruby` line, and loads the first one it finds; a `.ruby-gemset` next to `.ruby-version` names the gemset. A `.rvmrc` is a shell script **sourced into your shell**, so it can run any command, and RVM asks `y[es], n[o], v[iew], c[ancel]` before loading one. It records your answer with a checksum of the file, and an edited file prompts again. RVM itself warns that `.rvmrc` needs trust, is slower and is not compatible with other Ruby managers, and suggests `rvm rvmrc to ruby-version`.

code

bash · 8 lines
bash
cat .rvmrc
# rvm use 4.0.7@billing --create

rvm rvmrc trusted .          # is this .rvmrc trusted?
rvm rvmrc to ruby-version    # writes .ruby-version + .ruby-gemset, deletes .rvmrc
cat .ruby-version .ruby-gemset
# ruby-4.0.7
# billing

go deeper

for a junior

Recall that RVM switches Ruby on cd from files like .ruby-version and .ruby-gemset, and that .rvmrc asks for trust.

for a middle

Explain the upward search, the file order with .rvmrc first, and why sourced shell code needs a checksum-based trust prompt.

for a senior

Treat an unreviewed .rvmrc as code execution on cd, and plan the move to .ruby-version so non-interactive callers and other tools agree.

for a principal

Decide which project files a team standardises on so RVM, rbenv and CI all read the same Ruby version.

## The hook on directory changes RVM switches Rubies automatically when you change directory, and it does so by installing itself into your shell's directory-change path: - in **bash** (and old zsh), RVM defines `cd`, `pushd` and `popd` as shell functions that call the builtin and then run a list of hooks; - in **zsh** newer than 4.3.4, it appends its function to `chpwd_functions`, zsh's own directory-change hook; - with `rvm_project_rvmrc=2` (or `prompt`) in `~/.rvmrc`, it runs the check before each prompt instead; - with `rvm_project_rvmrc=0`, the hook is disabled. Because the hook lives in the interactive shell, it never runs in cron, systemd units or scripts that do not source RVM. ## Which file wins After a directory change, RVM starts at the new directory and walks upward until it reaches `$HOME` or the filesystem root. In each directory it checks these candidates in order and loads the first non-empty one: 1. `.rvmrc` 2. `.versions.conf` 3. `.ruby-version` (plus the legacy `.rbfu-version` and `.rbenv-version`) 4. `Gemfile`, only if it has a `#ruby=` comment or a `ruby` line `~/.rvmrc` is skipped as a project file: that path is RVM's **user settings** file, not a project file. Leaving every project directory restores the Ruby you had before entering, or the default. | File | Content | Needs trust | Read by other managers | |---|---|---|---| | `.rvmrc` | a bash script, usually `rvm use ...` | yes | no | | `.versions.conf` | `ruby=` and `ruby-gemset=` lines | no | no | | `.ruby-version` + `.ruby-gemset` | a version string; a gemset name | no | `.ruby-version` only | | `Gemfile` | a `ruby` line or `#ruby=` comment | no | Bundler reads the `ruby` line | For the data files, RVM uses the Ruby named in the file and creates the gemset if it is missing. ## Why `.rvmrc` needs trust A `.rvmrc` is **sourced** into your running shell: every line executes with your user's permissions, in your shell, the moment you `cd` into the directory. A cloned repository with a malicious `.rvmrc` would otherwise run its code as soon as you entered it. RVM's defence is a trust prompt: - the first time it meets a `.rvmrc`, RVM shows a notice and asks `y[es], n[o], v[iew], c[ancel]`; `v` prints the file; - your answer is stored in `~/.rvm/user/rvmrcs` together with an md5 and a sha256 checksum of the path and contents; - if the file changes, the checksum no longer matches and RVM asks again; - with no terminal on standard input, RVM does not prompt and the file is not loaded. `rvm rvmrc trust`, `untrust`, `trusted` and `reset` manage the stored answer; `rvm rvmrc load` loads a file and implicitly trusts it. ## Migrating away from `.rvmrc` When RVM loads a `.rvmrc` in a terminal it prints a warning (silenced per file with `rvm rvmrc warning ignore`) that the file requires trusting, is slower and is not compatible with other Ruby managers, and it names the fix: `rvm rvmrc to ruby-version`. That command loads the `.rvmrc`, writes `.ruby-version` for the Ruby it selected (plus `.ruby-gemset` when a gemset is in use) and then deletes the `.rvmrc`. Two details to check afterwards: - commit the deletion too: a `.rvmrc` restored from version control would again win over `.ruby-version`; - the conversion keeps only the Ruby and gemset, so any extra commands the old script ran (exporting variables, running `bundle install`) are dropped. RVM can load variables from a `.ruby-env` file next to `.ruby-version` (behind the same trust prompt), and a setup script can cover the rest. Other managers such as rbenv read `.ruby-version` and ignore `.ruby-gemset`, so the migrated project works for teammates who do not use RVM. ## Common mistakes - **Trusting a `.rvmrc` without viewing it.** Choosing `v` first costs a few seconds; the file runs with your full user permissions every time you enter the directory. - **Expecting a trust answer to follow the file.** Trust is keyed to the file's path and checksum on your machine, so a teammate, a CI runner or a fresh clone in another directory is asked again, or, with no terminal, silently skips the file. - **Leaving a stale `.rvmrc` in a parent directory.** Because the search walks upward, a forgotten `.rvmrc` in `~/code` applies to every project below it that has no project file of its own. - **Disabling the hook and forgetting.** With `rvm_project_rvmrc=0` set, nothing switches on `cd`, and the project files are only read when you ask RVM explicitly.

  • Why does a deploy script that runs `cd /srv/billing` not switch Ruby even though your SSH session does?
    The switch comes from RVM's hook, a shell function installed when an interactive shell sources RVM. A script run by cron, systemd or `sh` never sourced it, so `cd` is the plain builtin and no project file is read. Scripts should name the Ruby explicitly, through a wrapper or `rvm in /srv/billing do ...`.
  • Why does RVM ignore `~/.rvmrc` when it walks up the tree for project files?
    `~/.rvmrc` (and `/etc/rvmrc`) is RVM's user or system settings file, holding variables such as `rvm_project_rvmrc=0` or `rvm_ignore_gemsets_flag=1`. It is read when RVM loads, so the project-file search explicitly skips it rather than treating your home directory as a project.

saying these in an interview costs you the question

  • Believes .ruby-version wins when a .rvmrc sits in the same directory
  • Thinks .rvmrc is a key-value config file rather than sourced shell code
  • Assumes trusting a .rvmrc once covers later edits to it
  • Expects the cd hook to run inside cron jobs and scripts
  • Thinks ~/.rvmrc is loaded as a project file for the home directory