Why Your Yarn App Suddenly Looks for Bun
You run bundle update, kick off a build, and asset precompilation stops on this:”
cssbundling-rails: Command install failed, ensure bun is installed
Tasks: TOP => assets:precompile => css:build => css:install
Except your application uses Yarn. It has always used Yarn. Nothing in the project references Bun, and nobody on the team added it.
This is not an exotic edge case. It can happen to ordinary Yarn applications, and it lingers because the fix has been merged upstream but never released. In this post, we’ll walk through why cssbundling-rails misidentifies your package manager, and how to unblock your build.
The Short Version
cssbundling-rails 1.4.3 added yarn.lock to the list of lockfiles that mark a project as a Bun project. While preparing to install JavaScript dependencies, cssbundling-rails determines that Bun should be used when the Bun binary exists in the PATH. The issue explored in this post occurs when two things are true at once: the project contains a yarn.lock file and the build environment happens to have Bun installed.
When both are true, the gem runs bun install against a project that has never used Bun. If that command fails, the rake task reports ensure bun is installed even though Bun is installed, because the message is assembled from the name of the command it just tried to run.
The fix is PR #172 , commit 466cd57, merged to main in July 2025. As of August 2026 it is still not in a released version, and 1.4.3 remains the latest gem. So the workaround is to pin to the commit rather than to a version:
gem "cssbundling-rails", github: "rails/cssbundling-rails", ref: "466cd57"
You are likely to hit this if all of the following are true:
- You use
cssbundling-railswith Yarn, andyarn.lockis committed. - Your resolved version is 1.4.3. It is currently the latest release, so
bundle updatelands you there by default. - Bun is reachable on
PATHwherever you runassets:precompile, whether that is a Docker build, CI, or a PaaS build step. This is easier to hit than it sounds: Render’s images ship Bun by default, plenty of custom base images install it alongside Node, and so does any developer who has ever tried Bun locally.
Note that your project does not need to use Bun in any way, and you do not need both a yarn.lock and a bun.lock. An ordinary Yarn application is enough, as long as the Bun binary happens to be sitting in the environment that runs the build.
How We Ran Into It
We hit this while moving a Rails application onto a newer Ruby base image. It had used Yarn for years. Bun had never entered the picture.
Everything built cleanly until assets:precompile, which stopped on ensure bun is installed. Our first assumption was that some dependency had quietly started pulling Bun in, so we went looking through package.json and the lockfile. Neither pointed to Bun. The gem had not failed to run a command; it had decided on its own to run the wrong one.
The answer was in a shared base image. A little above the Ruby setup, it installed Bun, so it would be available to other projects using it, and put it on the path:
RUN curl -fsSL https://bun.com/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
Our project did not need Bun, but every build now ran with Bun in PATH. Those two lines (above) are what turned a quirk in a gem’s detection logic into a failed build.
That reframed the investigation. The question was not “why is Bun broken”, it was “why is a Yarn application attempting to use Bun?”
What’s Actually Happening
The task graph in the error message is the first clue:
assets:precompile => css:build => css:install
cssbundling-rails hooks into assets:precompile and runs css:build, which depends on css:install, which shells out to a package manager to install your JavaScript dependencies. The gem picks that package manager automatically, based on which lockfiles it finds in the project.
For the precompile path, that decision lives in Cssbundling::Tasks, in lib/tasks/cssbundling/build.rake . The relevant parts of version 1.4.3 looks like this:
LOCK_FILES = {
bun: %w[bun.lockb bun.lock yarn.lock],
yarn: %w[yarn.lock],
pnpm: %w[pnpm-lock.yaml],
npm: %w[package-lock.json]
}
def install_command
case
when using_tool?(:bun) then "bun install"
when using_tool?(:yarn) then "yarn install"
when using_tool?(:pnpm) then "pnpm install"
when using_tool?(:npm) then "npm install"
else raise "cssbundling-rails: No suitable tool found for installing JavaScript dependencies"
end
end
def tool_exists?(tool)
system "command -v #{tool} > /dev/null"
end
def using_tool?(tool)
tool_exists?(tool) && LOCK_FILES[tool].any? { |file| File.exist?(file) }
end
Two details: yarn.lock sits in the Bun list, so every Yarn project matches the Bun branch. And using_tool? joins both checks with &&, so the Bun match only wins when tool_exists? actually finds the bun binary. With Bun absent, the check falls through to Yarn and everything works, which is why this bug stays invisible on a plain ruby:* image. With Bun present, install_command returns bun install for a project that has never used Bun.
From there the misclassification lands in one of two ways. If bun install succeeds, it quietly writes a bun.lock into the repository alongside your yarn.lock, which is what most of the upstream reports describe. If it fails, the css:install task raises:
task :install do
command = Cssbundling::Tasks.install_command
unless system(command)
raise "cssbundling-rails: Command install failed, ensure #{command.split.first} is installed"
end
end
That is the message we saw, and it is built from command.split.first. It points you at Bun as the problem, when Bun was never part of the application to begin with.
Where the Regression Came From
The git history explains how a change meant to help Bun users introduced a bug that impacted Yarn users.
PR #165 , “Improve package manager detection for Bun 1.2 compatibility”, was merged in March 2025 and shipped in 1.4.3. Bun 1.2 made a new text-based bun.lock the default lockfile in place of the older binary bun.lockb, and bun install --yarn produces a yarn.lock. To cover those cases, the PR broadened Bun detection to accept bun.lockb, bun.lock, or yarn.lock.
That last option is the problem. Every Yarn project has a yarn.lock, so detection meant for a narrow Bun workflow started capturing ordinary Yarn projects too, on any machine where Bun was installed.
PR #172 , “Remove yarn from bun detect tool”, commit 466cd57, was merged in July 2025 to correct it. It stops treating yarn.lock as a Bun signal, so a Yarn project is detected as Yarn again.
It is worth knowing that this was not a simple typo. Issue #183 is still open and asks for the opposite behavior: Bun preferred for freshly generated applications. The same few lines are being pulled in two directions, and there is no clear consensus upstream on how it should behave.
Merged Is Not Yet Released
Unfortunately the usual “just bump the version” instinct does not work in this case. The latest released gem is 1.4.3, tagged March 3, 2025. The fix, 466cd57, was merged to main on July 10, 2025, but not yet released. The only way to get the fix today is to point Bundler at the commit itself:
# cssbundling-rails 1.4.3 misdetects bun for yarn projects (regression from
# PR #165). The fix is PR #172 / 466cd57, merged to main but unreleased as of
# August 2026. Unpin only once a release LATER than 1.4.3 ships with that commit.
gem "cssbundling-rails", github: "rails/cssbundling-rails", ref: "466cd57"
As a side note, when you pin a gem to a commit, it is a good idea to add a comment that describes the condition that makes it safe to remove. In this case, once a release later than 1.4.3 ships with commit 466cd57. A comment like ‘unpin after v1.4.3’ is easy to misread and does not explain why the dependency was pinned in the first place.
Now we can bundle install, and confirm that the Gemfile.lock records a GIT source pointing at the commit:
GIT
remote: https://github.com/rails/cssbundling-rails.git
revision: 466cd57...
specs:
cssbundling-rails (1.4.3)
Rebuild, and precompilation detects Yarn again. That one-line pin unblocked the applications that still needed a CSS build.
Conclusion
The alarming ensure bun is installed message is not about Bun at all, it is an ordinary Yarn application tripping over a detection change in cssbundling-rails 1.4.3, in an environment that happened to have Bun lying around. The fix exists upstream, it is simply stuck there, so a commit pin is the pragmatic move until a new version ships. A shared base image is a dependency too, so check it when a build breaks over something your code never asked for.
Snags like this one slowing down your Rails upgrade? We can help .