After about 10 years, I had to break up with RVM – mostly because a third party (Claude Code) wouldn’t cooperate.
RVM works through shell functions. When you install it, it wraps your cd command with a custom function that checks .ruby-version on every directory change. This only works in interactive shells that have loaded .zshrc, where RVM had a chance to set this up.
rbenv works through shims. It puts a directory of tiny wrapper scripts (~/.rbenv/shims/) at the front of your PATH. Every time you call ruby, bundle, rake, or any ruby binary, you’re actually calling a shim. The shim reads .ruby-version from the current directory (walking up the tree if needed) and delegates to the right ruby version. It relies on standard PATH behavior rather than shell hooks.
Where this starts to matter
Any tool that spawns subshells – CI runners, editors, AI coding assistants – often uses non-interactive shells that skip .zshrc. With RVM, that means version switching won’t happen unless you explicitly source it. With rbenv, as long as ~/.rbenv/shims is in PATH, everything behaves the same as in your terminal.
This was the original motivation: Claude Code spawns a fresh non-interactive subshell per Bash call, so RVM’s setup never ran and version switching simply didn’t happen.




Recent Comments