fix(actionagent): install and mount cleanly on MySQL and acronym hosts - #359
Merged
Merged
Conversation
Two things stopped a plain `mount ActionAgent::Engine` from working outside
a PostgreSQL host with default inflections. Both were found installing the
engine into a Rails 8 app on MySQL 8 that declares `inflect.acronym "API"`.
Migrations no longer set a default on JSON columns outside PostgreSQL.
Both templates already chose the column type per adapter — jsonb on
PostgreSQL, json elsewhere — but kept `default: []` / `default: {}` for
every adapter, and MySQL rejects a default on a JSON column outright
("BLOB, TEXT, GEOMETRY or JSON column can't have a default value"). That
aborts create_table, so `rails g action_agent:install && rails db:migrate`
could not complete: 3 columns in the traces migration, 31 in the dashboard
one. The paired `null: false` goes with the default, since nothing assigns
those attributes before validation and a NOT NULL column with no default
would reject the inserts the default existed to satisfy. Every JSON column
is read through `Array(...)` / `|| {}`, so a NULL reads as the empty value.
The api namespace now resolves under either spelling of the constant. An
engine's files are autoloaded by the host's `rails.main` loader under the
host's inflections, so a host that declares the API acronym made Zeitwerk
expect ActionAgent::API::TracesController from a file defining
ActionAgent::Api::TracesController, and every request to the mount raised
Zeitwerk::NameError. Rails also resolves a route's controller by camelizing
the stored path with the host's global inflections, which no engine-level
setting scopes, so fixing the autoloader alone still left the router
raising NameError. No single module name fixes both kinds of host — a plain
host camelizes api to Api, an acronym host to API — so the autoloader is
pinned to Api for this engine's own path only, and the namespace answers to
API as well.
The two gems in this repo share one release tag but version independently, so a release that patches only one of them rebuilds the other at its current version. `gem push` rejects that duplicate with a non-zero status, and because both pushes are one shell block, the rejection takes down the gem that actually changed. 1.2.1 is the first release to hit this: it patches actionagent alone, leaving activeagent at the 1.2.0 already published.
TonsOfFun
marked this pull request as ready for review
August 14, 2026 21:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things stopped a plain
mount ActionAgent::Enginefrom working outside a PostgreSQL host with default inflections. Both were found installing the engine into a Rails 8 app on MySQL 8 that declaresinflect.acronym "API", where each was fatal: the first aborteddb:migrate, the second made every request to the mount raise.Releases as 1.2.1. (The CHANGELOG still described 1.2.0 as Unreleased although it is on RubyGems; that heading is corrected here too.)
Migrations no longer set a default on JSON columns outside PostgreSQL
Both templates already chose the column type per adapter —
jsonbon PostgreSQL,jsonelsewhere — but keptdefault: []/default: {}for every adapter. MySQL rejects a default on a JSON column outright:That aborts
create_table, sorails g action_agent:install && rails db:migratecould not complete on any MySQL host: 3 columns in the traces migration, 31 in the dashboard one.The paired
null: falseis dropped along with the default on those adapters. Nothing in the models assigns these attributes before validation, so a NOT NULL column with no default would reject the very inserts the default existed to satisfy. Every JSON column is already read throughArray(...)/|| {}, so a NULL reads as the empty value.The api namespace resolves under either spelling of the constant
An engine's files are autoloaded by the host's
rails.mainloader, under the host's inflections. A host declaring theAPIacronym made Zeitwerk expectActionAgent::API::TracesControllerfrom a file definingActionAgent::Api::TracesController:Rails separately resolves a route's controller by camelizing the stored path (
"action_agent/api/traces") with the host's global inflections, which no engine-level setting scopes — so fixing the autoloader alone still left the router raisingNameError: uninitialized constant ActionAgent::API.No single module name fixes both kinds of host: a plain host camelizes
apitoApi, an acronym host toAPI. So the autoloader is pinned toApifor this engine's own path only — scoped by path rather than throughinflect, since the loader is shared and a blanket rule would re-spell the host's ownAPIconstants — and the namespace answers toAPIas well, viaconst_missingso the controllers stay lazily autoloaded.Verification
Installed into a Rails 8.1 / MySQL 8 app that declares the
APIacronym. Before:db:migrateaborted on the first JSON column. After: migrations complete,POST <mount>/api/tracesreturns 401 without a bearer token and 202 with one, and the dashboard renders. The host app needs no inflection workaround — confirmed by removing the workarounds it previously required and re-running.Note that
bundle exec rake testdoes not run on this checkout, on this branch or on a cleanmain: the dummy app fails to boot withcannot load such file -- active_storage/engine. That is pre-existing and unrelated, but it means CI is the first real run of the suite against these changes.Also: the release workflow skips an already-published version
Releasing this exposed a problem in
release.yml. Both gems in this repo share one release tag but version independently, so a release that patches only one of them rebuilds the other at its current version — here,activeagentstays at the 1.2.0 already on RubyGems.gem pushrejects that duplicate with a non-zero status, and because both pushes sit in one shell block, the rejection would take down theactionagentpush that follows it.The publish step now checks RubyGems for the built version and skips it if present, so a single-gem patch release publishes the gem that changed and leaves the other alone. 1.2.1 is the first release to hit this.