Code tried to mutate a frozen String in place (with <<, gsub!, concat, etc.). Ruby 3 and the frozen_string_literal magic comment freeze string literals, so any in-place mutation raises FrozenError.
What this error means
A test fails with FrozenError on a mutating string method. The same code may have worked on an older Ruby or in a file without the frozen_string_literal comment.
Bundler resolves against the Ruby version and the platform recorded in the lockfile. A runner on a different Ruby or a Linux platform missing from Gemfile.lock fails in a way that names a gem rather than the cause.
Terminal
ruby -v && bundle -v
cat .ruby-version 2>/dev/null
bundle platform
# the usual CI-only failure: Linux platform absent from the lockfile
bundle lock --add-platform x86_64-linux
bundle install --jobs 4 --retry 3
Common causes
frozen_string_literal magic comment
A # frozen_string_literal: true comment at the top of the file freezes every string literal in it, so in-place mutation of a literal raises.
Ruby version default change
CI runs a newer Ruby where more strings are frozen by default, exposing mutation that older versions tolerated.
Mutating a shared constant string
A frozen constant String is mutated in place rather than duped first.
How to fix it
Build a new string instead of mutating
Replace in-place mutation (<<, gsub!, concat) with non-mutating forms (+, gsub) that return a new String.
Or dup the literal before mutating it: name = +"report-" (the unary + gives a mutable copy).
Keep the frozen_string_literal comment; it is the recommended default.
Dup a frozen string when mutation is needed
Ruby
s = +"report-" # mutable copy of a frozen literal
s << id.to_s
How to prevent it
Prefer non-mutating string operations.
Use unary + or String.new when a mutable buffer is required.
Test on the same Ruby version CI runs.
Frequently asked questions
What causes FrozenError: can't modify frozen string in CI?
There are 3 common causes: frozen_string_literal magic comment, ruby version default change, and mutating a shared constant string. A # frozen_string_literal: true comment at the top of the file freezes every string literal in it, so in-place mutation of a literal raises.
How do I fix FrozenError: can't modify frozen string in CI?
There are 2 fixes depending on which cause you have: build a new string instead of mutating and dup a frozen string when mutation is needed. Work through them in order, since the first is the most common.
What does FrozenError: can't modify frozen string in CI actually mean?
A test fails with FrozenError on a mutating string method.
How do I stop FrozenError: can't modify frozen string in CI happening again?
Prefer non-mutating string operations. The prevention section lists 3 changes that keep it from recurring.