Skip to content
Latchkey

FrozenError: can't modify frozen String in CI

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.

ruby
FrozenError:
       can't modify frozen String: "report-"
     # ./app/services/report_namer.rb:7:in `<<'

Diagnose it: Ruby version and platform

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

  1. Replace in-place mutation (<<, gsub!, concat) with non-mutating forms (+, gsub) that return a new String.
  2. Or dup the literal before mutating it: name = +"report-" (the unary + gives a mutable copy).
  3. 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.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card