Summary
Booting a Rails 8 app on workos 7.0.0 with config.eager_load = true (default in production and most CI test envs) crashes during initialization with:
Zeitwerk::Cref#get
/vendor/bundle/ruby/4.0.0/gems/zeitwerk-2.7.4/lib/zeitwerk/cref.rb:62:in `Module#const_get': uninitialized constant WorkOS::Inflections (NameError)
@mod.const_get(@cname, false)
^^^^^^^^^^
...
/vendor/bundle/ruby/4.0.0/gems/zeitwerk-2.7.4/lib/zeitwerk/loader/eager_load.rb:173
/vendor/bundle/ruby/4.0.0/gems/zeitwerk-2.7.4/lib/zeitwerk/loader.rb:431:in `Zeitwerk::Loader.eager_load_all'
/vendor/bundle/ruby/4.0.0/gems/railties-8.1.3/lib/rails/application/finisher.rb:79
...
Repro
Any Rails 8 app:
- Add
gem "workos", "~> 7.0" to Gemfile
- Run
RAILS_ENV=test bundle exec rails runner "Rails.application.eager_load!; puts 'OK'"
- NameError as above.
(Lazy-load test envs that don't eager-load — like a basic bin/rails t on a single file — pass, which is why this can sneak past local development and only surface in CI / production deploys.)
Root cause
lib/workos/inflections.rb defines a top-level constant WORKOS_INFLECTIONS (a hash used by loader.inflector.inflect(...)):
# lib/workos/inflections.rb
WORKOS_INFLECTIONS = {
"authenticate_response_oauth_token" => "AuthenticateResponseOAuthToken",
...
}
But the file lives at lib/workos/inflections.rb, so Zeitwerk's path-to-constant inference expects it to define WorkOS::Inflections.
lib/workos.rb require_relatives the file before loader.setup, which works for the lazy-load path (the gem can run). But Zeitwerk::Registry still tracks the gem's loader, and when Rails calls Zeitwerk::Loader.eager_load_all it iterates every registered loader (including third-party gems') and tries to const_get the expected constant for each managed file. That const_get fails for inflections.rb.
lib/workos.rb already calls loader.ignore("#{__dir__}/workos/errors.rb") for similar reasons — inflections.rb needs the same treatment.
Suggested fix (one line)
In lib/workos.rb:
loader.collapse("#{__dir__}/workos/widgets")
+ loader.ignore("#{__dir__}/workos/inflections.rb")
loader.ignore("#{__dir__}/workos/errors.rb")
loader.setup
Happy to open a PR if useful.
Workaround in consuming apps (until released)
# config/initializers/workos.rb
require "workos/configuration"
workos_gem_path = Gem.loaded_specs["workos"]&.full_gem_path
if workos_gem_path
workos_loader = Zeitwerk::Registry.loaders.each.find { |l| l.dirs.any? { |d| d.start_with?(workos_gem_path) } }
workos_loader&.do_not_eager_load(File.join(workos_gem_path, "lib/workos/inflections.rb"))
end
WorkOS.configure do |config|
config.api_key = ENV.fetch("WORKOS_API_KEY", nil)
config.client_id = ENV.fetch("WORKOS_CLIENT_ID", nil)
end
Environment
workos: 7.0.0
zeitwerk: 2.7.4
rails: 8.1.3
ruby: 4.0.1
Summary
Booting a Rails 8 app on
workos 7.0.0withconfig.eager_load = true(default in production and most CI test envs) crashes during initialization with:Repro
Any Rails 8 app:
gem "workos", "~> 7.0"toGemfileRAILS_ENV=test bundle exec rails runner "Rails.application.eager_load!; puts 'OK'"(Lazy-load test envs that don't eager-load — like a basic
bin/rails ton a single file — pass, which is why this can sneak past local development and only surface in CI / production deploys.)Root cause
lib/workos/inflections.rbdefines a top-level constantWORKOS_INFLECTIONS(a hash used byloader.inflector.inflect(...)):But the file lives at
lib/workos/inflections.rb, so Zeitwerk's path-to-constant inference expects it to defineWorkOS::Inflections.lib/workos.rbrequire_relatives the file beforeloader.setup, which works for the lazy-load path (the gem can run). ButZeitwerk::Registrystill tracks the gem's loader, and when Rails callsZeitwerk::Loader.eager_load_allit iterates every registered loader (including third-party gems') and tries toconst_getthe expected constant for each managed file. That const_get fails forinflections.rb.lib/workos.rbalready callsloader.ignore("#{__dir__}/workos/errors.rb")for similar reasons —inflections.rbneeds the same treatment.Suggested fix (one line)
In
lib/workos.rb:loader.collapse("#{__dir__}/workos/widgets") + loader.ignore("#{__dir__}/workos/inflections.rb") loader.ignore("#{__dir__}/workos/errors.rb") loader.setupHappy to open a PR if useful.
Workaround in consuming apps (until released)
Environment
workos: 7.0.0zeitwerk: 2.7.4rails: 8.1.3ruby: 4.0.1