Skip to content

Ruby upgrade writes outside its new keg during `make install`

Source: Troubleshooting reference

Symptom: upgrading vim, ccache, or another Ruby dependent builds Ruby and then fails under the global Homebrew Ruby directory:

Dir.mkdir: Permission denied @ dir_s_mkdir - .../brew/lib/ruby

The same contamination can later surface as Errno::ENOENT under .../brew/lib/ruby/gems/... during RubyGems setup.

Root cause: the formula adds the versioned [email protected] path as a compatibility fallback. runruby supplies the build directory through LD_LIBRARY_PATH, but Homebrew’s GCC emits DT_RPATH, which the dynamic loader searches first. During make install, the new Cellar lib directory is not populated yet, so the build executable falls through to the previous keg’s libruby. The source RUBYLIB also lacks RubyGems’ optional defaults/operating_system.rb; its require can therefore find the previous keg’s file. Those old Homebrew defaults redirect Gem.default_dir and Gem.ruby outside the new keg. The Linux filesystem sandbox correctly rejects that write; the prefix permissions are not broken.

Fix: install/patch-homebrew-ruby.sh patches the local formula before Brew Bundle runs. It enables new ELF dtags so DT_RUNPATH yields to the build-tree library path, retains the new keg before the versioned fallback after install, and adds a build-local empty RubyGems packager-default file so the previous keg’s override cannot leak in. The formula replaces that empty file with the current Homebrew configuration after installation.

This formula-local DT_RUNPATH is a deliberate exception to the prefix’s usual DT_RPATH policy: the build runner must let its temporary LD_LIBRARY_PATH select the new build-tree libruby.

Do not chmod the prefix or disable Homebrew’s Linux sandbox. Wait for any active Homebrew process to exit, then rerun ~/dotfiles/bootstrap.sh upgrade so the formula refresh and patch happen in the intended order.