Skip to content

Fix rbenv-built rubies silently running as Homebrew's libruby - #75

Merged
JPDuchesne merged 2 commits into
mainfrom
jpd/rbenv-libruby-rpath-hijack
Aug 1, 2026
Merged

Fix rbenv-built rubies silently running as Homebrew's libruby#75
JPDuchesne merged 2 commits into
mainfrom
jpd/rbenv-libruby-rpath-hijack

Conversation

@JPDuchesne

Copy link
Copy Markdown
Contributor

Summary

Root cause of the cellbound-3d#151 CI failure (Your Ruby version is 4.0.6, but your Gemfile specified 4.0.5), diagnosed with a throwaway workflow on the ephemeral hosted runner: dev provisioned rbenv Ruby 4.0.5 correctly, shadowenv exec applied the right PATH, command -v ruby resolved to the 4.0.5 binstub — and ruby -v still printed 4.0.6.

The mechanism is a same-soname shared-library hijack that dev builds into every rbenv Ruby itself:

  • ruby_build_env bakes -Wl,-rpath,<brew>/lib into the build (needed so Linuxbrew-linked libs like libcrypt.so.2 resolve at runtime).
  • Rubies differing only in teeny share a libruby soname (libruby.so.4.0), and Linuxbrew's lib dir carries its own Ruby's libruby.
  • The brew rpath entry precedes the ruby's own libdir in the runtime search order, so the freshly built 4.0.5 binary loads — and runs as — brew's 4.0.6 libruby. Bundler then truthfully reports the pin mismatch.

macOS is unaffected (dyld links libruby by absolute install name), which is why this only ever bites Linux boxes whose linuxbrew Ruby shares the minor version. cellbound-3d#107's "bundle exec runs under Linuxbrew Ruby" symptom is the adjacent seam, but this one corrupts even correctly-shadowenv'd invocations.

Changes

  • ruby_build_env now rpaths the build's own lib dir ahead of brew's, so its libruby always wins; brew's dir stays for libcrypt et al. (Signature gains the version to compute the install prefix.)
  • ensure_ruby_installed! treats a wrong reported version like a crippled extension: force-rebuild an existing hijacked install, and loudly abort (with the fix command) if the freshly built ruby still runs as another version — instead of surfacing later as a cryptic bundler error.

Test plan

  • New RSpock tests: rpath ordering, reported_ruby_version, verify_reported_version! abort, hijack force-rebuild path, healthy fast path (red before the fix, green after)
  • Full suite: 750 tests, 0 failures; rubocop + srb clean
  • After release: retrigger cellbound-3d#151 — the hosted runner rebuilds 4.0.5 with the corrected rpath and dev up proceeds past bundler

Made with Cursor

The brew-lib rpath baked into rbenv builds (for libcrypt on Linuxbrew) is
searched before the ruby's own lib dir; since rubies differing only in
teeny share a libruby soname, a same-minor brew Ruby hijacks the build —
a 4.0.5 install runs as 4.0.6 and bundler fails the Gemfile ruby pin.
Rpath the build's own lib dir first, verify the installed binary reports
the requested version, and force-rebuild hijacked installs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

codecov/patch flagged the two changed lines no test executed: the
missing-extension rebuild branch and the real rbenv install call. Cover
the latter with a fake rbenv on a restricted PATH instead of mocking, so
the build-env plumbing actually runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JPDuchesne
JPDuchesne merged commit 6780e8d into main Aug 1, 2026
4 checks passed
@JPDuchesne
JPDuchesne deleted the jpd/rbenv-libruby-rpath-hijack branch August 1, 2026 18:28
JPDuchesne added a commit that referenced this pull request Aug 1, 2026
6780e8d Merge pull request #75 from d3mlabs/jpd/rbenv-libruby-rpath-hijack
25fd75e Cover the rbenv install invocation and extension-rebuild message
a11448e Fix rbenv-built rubies silently running as Homebrew's libruby
@JPDuchesne

JPDuchesne commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

/learn the root cause here, not the code change: rbenv-built rubies with a Homebrew lib dir rpathed ahead of their own libdir get their libruby hijacked by a same-minor brew Ruby (shared soname libruby.so.X.Y), so the binary silently runs as the other version — bundler then fails the Gemfile ruby pin with a mismatch that looks like a project bug. Diagnosis cue: command -v ruby resolves to the right binstub but ruby -v reports another version. Only bites Linux (macOS dyld links libruby by absolute install name).

/learn — drafted 1 learning in a draft PR: #77

  • rbenv-libruby-rpath-hijack

⚙️ workflow run · model: claude-fable-5-high

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant