I was tracking down an issue I had seen locally, and then I noticed a similar report yesterday in #9546, so I broadened the scope of my investigation somewhat.
Describe the problem as clearly as you can
My original issue: if BUNDLE_FROZEN=1 bundle install is run on a lockfile that contains unexpected/orphan entries in the CHECKSUMS section (i.e. gem listed in CHECKSUMS but not in GEM), bundler will print Cannot write a changed lockfile while frozen. to stderr, then proceeds anyway with the install, and exits 0.
Upon further investigation (with Claude) there are quite a number of similar other cases:
- orphaned entry in the GEM section (i.e. not in DEPENDENCIES and not a transitive dep)
- duplicate entry in the GEM section (two different versions listed of the same gem)
- PLATFORM entries listed in the wrong order
And probably others that I haven't looked at... claude suggests
any other lockfile content that to_lock would normalize away — stale GIT/PATH source blocks
for gems no longer used, ordering of dependencies under a source, missing trailing newline, etc. — should fall
into the same trap, since they all go through the same final write path
Did you try upgrading rubygems & bundler?
Reproduced on latest git commit 5869518.
Post steps to reproduce the problem
FROM rubylang/ruby:4.0.4
ARG BUNDLER_VERSION=4.0.11
RUN gem install bundler:${BUNDLER_VERSION} --no-document
RUN useradd --create-home --shell /bin/bash app \
&& mkdir -p /app && chown app:app /app
USER app
WORKDIR /app
RUN cat > Gemfile <<'EOF'
source "https://rubygems.org"
gem "rake", "13.2.1"
EOF
RUN bundle config set --local path 'vendor/bundle' \
&& bundle install \
&& bundle lock --add-checksums \
&& cp Gemfile.lock Gemfile.lock.bak
# Inject an orphan gem ("json") into the lockfile: it is listed in GEM and
# CHECKSUMS, but is NOT in DEPENDENCIES and is NOT a transitive dependency of
# rake. The lockfile is therefore self-inconsistent — `to_lock` would prune it.
RUN sed -i \
-e '/^ rake (13.2.1)$/a\ json (2.7.0)' \
-e '/^ rake (13.2.1) sha256=/a\ json (2.7.0) sha256=1f64f8b32a3a570286a32fb203b863b6233aa1da6193ab0648fe7e35aa17fb09' \
Gemfile.lock
CMD set +e; \
echo "=== BUNDLE_FROZEN=${BUNDLE_FROZEN} ==="; echo; \
echo "=== bundle --version ==="; bundle --version; echo; \
echo "=== Gemfile.lock diff ==="; diff -u Gemfile.lock.bak Gemfile.lock; echo; \
echo "=== bundle check ==="; bundle check; echo "=> exit=$?"; echo; \
echo "=== bundle install ==="; bundle install; echo "=> exit=$?"
Build and run:
docker build -t bundler-repro .
docker run --rm --env BUNDLE_FROZEN=1 bundler-repro
Which command did you run?
BUNDLE_FROZEN=1 bundle install
What were you expecting to happen?
bundler should refuse to install, and exit non-zero in frozen mode if the lockfile is invalid in some way
(Or, arguably, some of these differences could be accepted as cosmetic, and not produce any stderr.)
What happened instead?
Cannot write a changed lockfile while frozen.
Bundle complete! 1 Gemfile dependency, 1 gem now installed.
Bundled gems are installed into `./vendor/bundle`
=> exit=0
Fix?
Claude suggested the following fix, which I will include for completeness, though I am not yet familiar enough with the bundler code base to validate it:
One change closes all of them: in definition.rb:415-418, replace `Bundler.ui.error "Cannot write a changed
lockfile while frozen."; return` with a `raise ProductionError`. That turns the existing canonical-form diff (which
bundler is already computing) into the authoritative frozen-mode check, instead of having a weaker Gemfile-only
comparison gating things and a real check that only logs.
I was tracking down an issue I had seen locally, and then I noticed a similar report yesterday in #9546, so I broadened the scope of my investigation somewhat.
Describe the problem as clearly as you can
My original issue: if
BUNDLE_FROZEN=1 bundle installis run on a lockfile that contains unexpected/orphan entries in the CHECKSUMS section (i.e. gem listed in CHECKSUMS but not in GEM), bundler will printCannot write a changed lockfile while frozen.to stderr, then proceeds anyway with the install, and exits 0.Upon further investigation (with Claude) there are quite a number of similar other cases:
And probably others that I haven't looked at... claude suggests
Did you try upgrading rubygems & bundler?
Reproduced on latest git commit 5869518.
Post steps to reproduce the problem
Build and run:
Which command did you run?
What were you expecting to happen?
bundler should refuse to install, and exit non-zero in frozen mode if the lockfile is invalid in some way
(Or, arguably, some of these differences could be accepted as cosmetic, and not produce any stderr.)
What happened instead?
Fix?
Claude suggested the following fix, which I will include for completeness, though I am not yet familiar enough with the bundler code base to validate it: