[BUG] ruby_llm:upgrade ignores model mappings when a class option comes first
Nobody has claimed this yet.
- 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
-
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). -
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 -
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) -
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 renameand--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.splitreproduces 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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