Six of the gem's fourteen runtime dependencies have no version constraint at all. agentic.gemspec:35-48:
spec.add_dependency "dry-schema" # unbounded
spec.add_dependency "ruby-openai" # unbounded
spec.add_dependency "zeitwerk" # unbounded
spec.add_dependency "ostruct" # unbounded
spec.add_dependency "logger" # unbounded
spec.add_dependency "cgi" # unbounded
spec.add_dependency "async", "~> 2.0"
spec.add_dependency "thor", "~> 1.2"
spec.add_dependency "tty-spinner", "~> 0.9"
# ...the rest of the tty-* family, all pessimistic
The tty-* gems got it right. The one that talks to OpenAI didn't.
What that costs, concretely
Gemfile.lock pins ruby-openai (7.1.0). RubyGems currently serves 8.3.0. CI tests 7.1.0. A developer running bundle add agentic today resolves 8.3.0, because nothing in the gemspec says otherwise. So every new user of this gem is running a major version of its most important dependency that the project has never once tested.
That's the semver contract inverted. WORLD.md says "Public API changes follow semver and deprecation cycles. People's agents run on this." An unbounded dependency means someone's agents can break on a bundle update that never touched agentic at all, and there's nothing you could have released to stop it.
I checked whether it's already broken
It isn't, today. bundle update ruby-openai (7.1.0 → 8.3.0, pulling faraday-net_http 3.3.0 → 3.4.4), then:
So OpenAI::Client.new(access_token:, uri_base:) and the whole OpenAI::Error hierarchy that lib/agentic/llm_client.rb:221-252 switches on survived the major bump intact.
Two honest caveats on that green run. The suite uses VCR and WebMock, so it proves Ruby-level API compatibility and says nothing about wire behavior against the real API. And it ran on Ruby 4.0.6, not CI's 3.2.4.
Which makes this the cheap moment rather than the urgent one. Right now the constraint can be added and the lock moved in the same breath, with a passing suite on both sides. Wait for a breaking 9.x and it's an incident report instead.
What I'd do
1. Bound the six. For each, the constraint that matches how the code actually uses it:
spec.add_dependency "ruby-openai", ">= 7.1", "< 9"
spec.add_dependency "dry-schema", "~> 1.13"
spec.add_dependency "zeitwerk", "~> 2.6"
spec.add_dependency "ostruct", "~> 0.6"
spec.add_dependency "logger", "~> 1.6"
spec.add_dependency "cgi", "~> 0.4"
ruby-openai gets a range rather than ~> deliberately: 7 and 8 are both verified working here, and narrowing to ~> 7.1 would strand every consumer already resolving 8.x. The other five get standard pessimistic bounds at their locked minors.
Adding a constraint where none existed can break resolution for a consumer sitting outside it, so this is semver-visible and wants a minor bump plus a CHANGELOG line. Your call, which is why it's an issue and not a PR.
2. Decide the lockfile separately. Whether dev/CI moves to ruby-openai 8.3.0 is a smaller question and I'm happy to take it once you've ruled on the constraint. My lean is yes: testing 7.1.0 while shipping consumers onto 8.x is the worst of both.
3. Note the interaction with logger and cgi. Those two are declared because they become bundled gems in Ruby 3.5 (the gemspec comments say so). They deserve bounds like anything else, but they're also the deps most likely to be affected by whatever floor gets picked in #15.
Related
Verification
Run against upstream/main at 99a9167 on Ruby 4.0.6. bundle outdated shows 40 gems behind, of which ruby-openai is the only unbounded runtime dep with a major available.
Filed by the loop:deps session. Labels: loop:deps, status:analyzed.
Six of the gem's fourteen runtime dependencies have no version constraint at all.
agentic.gemspec:35-48:The
tty-*gems got it right. The one that talks to OpenAI didn't.What that costs, concretely
Gemfile.lockpinsruby-openai (7.1.0). RubyGems currently serves 8.3.0. CI tests 7.1.0. A developer runningbundle add agentictoday resolves 8.3.0, because nothing in the gemspec says otherwise. So every new user of this gem is running a major version of its most important dependency that the project has never once tested.That's the semver contract inverted. WORLD.md says "Public API changes follow semver and deprecation cycles. People's agents run on this." An unbounded dependency means someone's agents can break on a
bundle updatethat never touched agentic at all, and there's nothing you could have released to stop it.I checked whether it's already broken
It isn't, today.
bundle update ruby-openai(7.1.0 → 8.3.0, pulling faraday-net_http 3.3.0 → 3.4.4), then:So
OpenAI::Client.new(access_token:, uri_base:)and the wholeOpenAI::Errorhierarchy thatlib/agentic/llm_client.rb:221-252switches on survived the major bump intact.Two honest caveats on that green run. The suite uses VCR and WebMock, so it proves Ruby-level API compatibility and says nothing about wire behavior against the real API. And it ran on Ruby 4.0.6, not CI's 3.2.4.
Which makes this the cheap moment rather than the urgent one. Right now the constraint can be added and the lock moved in the same breath, with a passing suite on both sides. Wait for a breaking 9.x and it's an incident report instead.
What I'd do
1. Bound the six. For each, the constraint that matches how the code actually uses it:
ruby-openaigets a range rather than~>deliberately: 7 and 8 are both verified working here, and narrowing to~> 7.1would strand every consumer already resolving 8.x. The other five get standard pessimistic bounds at their locked minors.Adding a constraint where none existed can break resolution for a consumer sitting outside it, so this is semver-visible and wants a minor bump plus a CHANGELOG line. Your call, which is why it's an issue and not a PR.
2. Decide the lockfile separately. Whether dev/CI moves to ruby-openai 8.3.0 is a smaller question and I'm happy to take it once you've ruled on the constraint. My lean is yes: testing 7.1.0 while shipping consumers onto 8.x is the worst of both.
3. Note the interaction with
loggerandcgi. Those two are declared because they become bundled gems in Ruby 3.5 (the gemspec comments say so). They deserve bounds like anything else, but they're also the deps most likely to be affected by whatever floor gets picked in #15.Related
Verification
Run against
upstream/mainat 99a9167 on Ruby 4.0.6.bundle outdatedshows 40 gems behind, of which ruby-openai is the only unbounded runtime dep with a major available.Filed by the
loop:depssession. Labels:loop:deps,status:analyzed.