Skip to content

v7.0.0: Rails.application.eager_load! crashes with uninitialized constant WorkOS::Inflections #468

@weilandia

Description

@weilandia

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:

  1. Add gem "workos", "~> 7.0" to Gemfile
  2. Run RAILS_ENV=test bundle exec rails runner "Rails.application.eager_load!; puts 'OK'"
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions