crmne / crmne/ruby_llm

[BUG] ruby_llm:upgrade ignores model mappings when a class option comes first

Open
#942 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Ruby
Stars
4.4k
Forks
504
Avg merge
1d 8h
Merged PRs (30d)
11

Description

Basic checks
  • I searched existing issues - this hasn't been reported
  • I can reproduce this consistently
  • This is a RubyLLM bug, not my application code
What's broken?

ruby_llm:upgrade silently drops the model mappings you pass on the command line
whenever a class option appears before them. The generator falls back to the
default Chat / Message / Model / ToolCall names and writes migrations
against tables the application does not have.

Affected forms (Thor gives the generator an empty positional list):

bin/rails generate ruby_llm:upgrade --mode copy chat:AI::Chat message:AI::Chat::Message
bin/rails generate ruby_llm:upgrade --mode=copy chat:AI::Chat message:AI::Chat::Message
bin/rails generate ruby_llm:upgrade --phase prepare chat:AI::Chat message:AI::Chat::Message
bin/rails generate ruby_llm:upgrade --mode rename chat:AI::Chat message:AI::Chat::Message

Putting the mappings first works:

bin/rails generate ruby_llm:upgrade chat:AI::Chat message:AI::Chat::Message --mode copy

The upgrade guide tells you to pass the same mappings and --mode for every
phase without fixing the order, so this is easy to hit. The same
argument :model_mappings, type: :array, default: [] declaration is used by
ruby_llm:install, ruby_llm:chat_ui, and ruby_llm:tool.

How to reproduce
  1. Create a Rails application with custom, namespaced chat models, e.g.
    AI::Chat, AI::Chat::Message, AI::LLMModel, AI::Chat::ToolCall, and a
    RubyLLM 1.16 schema (ai_chats, ai_chat_messages, ai_llm_models,
    ai_chat_tool_calls).

  2. Generate the upgrade:

    bin/rails generate ruby_llm:upgrade --mode copy \
      chat:AI::Chat \
      message:AI::Chat::Message \
      model:AI::LLMModel \
      tool_call:AI::Chat::ToolCall
    
  3. The generator reports the default names and writes migrations for the wrong
    tables:

    models  chat:Chat -> chats, message:Message -> messages, model:Model -> models, tool_call:ToolCall -> tool_calls
    
    # db/migrate/..._prepare_ruby_llm_v2_upgrade.rb
    move_table(:models, :ruby_llm_models)
    move_table(:tool_calls, :ruby_llm_tool_calls)
    
    # config/initializers/ruby_llm_upgrade.rb
    RubyLLMUpgrade.install(chat: Chat, message: Message, tool_call: tool_call)
    
  4. Run bin/rails db:migrate.

Expected behavior

The mappings on the command line are honored wherever --mode / --phase
appear, so the generator resolves AI::Chat -> ai_chats and writes
move_table(:ai_llm_models, :ruby_llm_models).

What actually happened

The mappings were ignored with no error at generation time. bin/rails db:migrate
then aborts on boot with an error that does not point at the cause:

NameError: uninitialized constant Chat

  RubyLLMUpgrade.install(chat: Chat, message: Message, tool_call: tool_call)
config/initializers/ruby_llm_upgrade.rb:12:in 'block in <top (required)>'

If the application happens to define a Chat constant, the prepare migration
then fails on validate_table_move(:models, :ruby_llm_models).

Root cause: Thor::Arguments.split collects positional arguments only until the
first token matching /^-/, and Thor::Group.dispatch uses that split
(thor-1.5.0/lib/thor/group.rb:234). With --mode copy chat:AI::Chat … the
positional list is empty. upgrade_generator.rb:32 declares
argument :model_mappings, type: :array, default: [], so an empty list is
accepted silently and parse_model_mappings starts from
MODEL_MAPPING_DEFAULTS. A required argument would have raised instead.

Environment
  • Baseline commit for the reported behavior: e7bd2c9a1a7fa49870bf4ed26f4e1480ad098e80
  • Original report environment: Ruby 4.0.2, Rails 8.1.3.1; the report also listed Rails 7.1.6
  • Current local validation: Ruby 3.3.5, Rails 8.1.3.1, SQLite
  • Provider: N/A
  • PostgreSQL was not exercised in the current validation; the failure is in Thor argument splitting before database access
  • Mode: both --mode rename and --mode copy
  • Phase: default multi-phase and every --phase
  • Model mapping: custom namespaced (chat:AI::Chat message:AI::Chat::Message model:AI::LLMModel tool_call:AI::Chat::ToolCall)
  • Inflections: inflect.acronym "AI", inflect.acronym "LLM"
  • OS: macOS arm64

Scope

This starts from a supported RubyLLM 1.16 setup and follows the documented 2.0
upgrade path. It is a command-line parsing defect, not an application
configuration problem: the same mappings resolve correctly when they precede the
options.

Local validation

  • Thor::Arguments.split reproduces the empty positional list for the affected command order.
  • bundle exec rspec spec/ruby_llm/generators/upgrade_generators_spec.rb: 12 examples, 0 failures.
  • bundle exec rspec --tag generator: 95 examples, 0 failures with the RubyLLM 1.16 compatibility gem.
  • The shared helper also has regression coverage for dasherized option names, option aliases, and boolean options.
  • PostgreSQL and a full production-style multi-phase database migration were not run; the existing generator suite does cover UUID paths on SQLite.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with upgrade_generator.rb:32 and the Thor argument handling described in the report, then inspect spec/ruby_llm/generators/upgrade_generators_spec.rb and the shared helper's existing regression coverage. Reproduce the option-before-mapping command order and add coverage showing that all model mappings are retained regardless of option placement; the generator should report the custom mappings and produce migrations for their tables.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
cli, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.