Skip to content

Conversation

@trevorturk
Copy link
Contributor

@trevorturk trevorturk commented Nov 2, 2025

Summary

Completes the fix from PR #59 to prevent crashes during eager loading in non-Rails contexts.

Problem

uninitialized constant RubyLLM::Rails (NameError)
  class Railtie < Rails::Railtie

Occurs when Zeitwerk eager loads (e.g., in gems that depend on ruby_llm but don't use Rails).

Root Cause

PR #59 added conditional require: require 'ruby_llm/railtie' if defined?(Rails::Railtie)

This is insufficient because Zeitwerk's eager loading happens before that check runs. When Zeitwerk loads railtie.rb, the class definition fails because Rails::Railtie doesn't exist.

Solution

Two complementary fixes (both required per Zeitwerk documentation):

1. Wrap class definition in if defined?(Rails::Railtie):

if defined?(Rails::Railtie)
  module RubyLLM
    class Railtie < Rails::Railtie
      # ...
    end
  end
end

2. Ignore from Zeitwerk:

loader.ignore("#{__dir__}/ruby_llm/railtie.rb")

Evidence

Testing

Before: Zeitwerk::Loader.eager_load_all → ❌ NameError
After: Zeitwerk::Loader.eager_load_all → ✅ Success

Thank you for reviewing!

## Problem

Applications using ruby_llm crash during eager loading (production boot):

```
uninitialized constant RubyLLM::Rails (NameError)
  class Railtie < Rails::Railtie
                  ^^^^^
```

This occurs when:
- `config.eager_load = true` (standard in production)
- Zeitwerk eager loads all files including railtie.rb
- railtie.rb references `Rails::Railtie` unconditionally
- Rails isn't loaded yet or app doesn't use Rails

##Root Cause

PR crmne#59 added conditional require (`require 'ruby_llm/railtie' if defined?(Rails::Railtie)`),
but this is insufficient because Zeitwerk's eager loading happens BEFORE that
conditional check runs.

When Zeitwerk eager loads, it loads railtie.rb and expects `RubyLLM::Railtie`
to be defined, but the class definition fails because `Rails::Railtie` doesn't exist.

## Solution

Two complementary fixes (both required):

**1. Wrap class definition** in `if defined?(Rails::Railtie)` (railtie.rb)
   - Prevents error when file is loaded without Rails
   - Pattern used by github/secure_headers gem

**2. Ignore from Zeitwerk** with `loader.ignore()` (ruby_llm.rb)
   - Prevents Zeitwerk from expecting a constant from this file
   - Per Zeitwerk docs: "files not following conventions" should be ignored

## Evidence

**Zeitwerk maintainer guidance** (issue crmne#143):
> "have the strategy defined in `lib`, perform a `require` in the initializer"

**Zeitwerk docs**:
> Use `loader.ignore()` for "files not following conventions"

**Real-world precedent** (github/secure_headers):
```ruby
if defined?(Rails::Railtie)
  module SecureHeaders
    class Railtie < Rails::Railtie
      # ...
    end
  end
end
```

## Testing

Before (ruby_llm 1.8.2):
```bash
$ bundle exec ruby -e "require 'swarm_sdk'; Zeitwerk::Loader.eager_load_all"
NameError: uninitialized constant RubyLLM::Rails
```

After (with both fixes):
```bash
$ bundle exec ruby -e "require 'swarm_sdk'; Zeitwerk::Loader.eager_load_all"
✓ SUCCESS
```

## Related

- PR crmne#59 - Partial fix (conditional require only)
- Zeitwerk issue crmne#143 - Guidance on conditional loading
- github/secure_headers - Real-world example of pattern
@crmne crmne merged commit fb62842 into crmne:main Nov 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants