Generated Rails
Hifumi generates a complete Rails application from a natural-language prompt. It runs a plain rails new, then a planning model breaks your request into revisions and Claude agents write each one. The prompt steers them toward a default Rails 8 app: Tailwind and Hotwire, the default Gemfile, and sign-in via has_secure_password plus sessions rather than Devise. Each revision has to pass a verify loop before it's committed: bundle check, db:prepare, zeitwerk:check, a smoke test that requests every static GET page, and rails test. What you get at the end is an ordinary Rails repository, yours to keep.
Roundhouse reads a Rails app and compiles it, to Ruby, to native code via Spinel, and to a dozen other targets. It also has a check command that reports what it can't compile.
Generated apps are an interesting thing to point a compiler at. They are recent Rails, written the way the guides say to write it, and there can be a lot of them. Hand-written apps drift away from the defaults over the years; generated ones stay close to them. So I spent a day on what happens when you put the two together.
The bug Hifumi already caught
Hifumi's notes describe a project, an Event RSVP app, that crashed in the preview with undefined method 'authenticate_user!' for an instance of EventsController. The agent had reached for Devise's filter without Devise being in the Gemfile. Rails loads the class happily and answers every guarded page with a 500.
Roundhouse's check said nothing about it, and that was worse than it sounds. The compiler, finding no method to call, dropped the filter. The compiled app didn't crash where Rails did; it ran the action with no guard in front of it.
check now reports it as an error:
app/controllers/events_controller.rb:2:17: error[undefined_filter_target]:
`before_action :authenticate_user!` names a method nothing defines;
Rails raises NoMethodError on every action it guards
It looks in the controller, its ancestors, the concerns they include, and the methods Rails itself provides (including Devise's, when a model declares devise). It stays quiet when it can't see everything: a superclass from a gem, an include of a module the app doesn't define. Run against Mastodon, Lobsters, Campfire and Discourse, it reports nothing, which is the point: an error here has to mean something.
But Hifumi had already closed this one on its own. Its route smoke check was built after that project, and it catches the crash by requesting /events/new. What a static check adds is the part a smoke test can't reach. A filter written only: %i[edit update destroy] guards no page you can request without an id, so the smoke test never sees it. check sees it regardless, and runs in a fraction of a second.
The part that wasn't easy
The real finding was elsewhere. I don't have any of Hifumi's output, so I built a stand-in: a fresh Rails 8 app with two scaffolds and the sign-in Hifumi's prompt asks for, produced the quickest way Rails offers, bin/rails g authentication. Whether Hifumi's agents run that generator or write the same thing by hand, I don't know. check reported zero errors. Compiling it with Spinel produced four refusals, and nothing was written.
One was the app's own fault: the generated Authentication concern calls root_url, and the scaffolds don't define a root route. Rails would raise there too. The other three were Roundhouse's:
User.authenticate_by(params.permit(:email_address, :password))User.find_by_password_reset_token!(params[:token]), twice
The analyzer knew the types of both. Nothing implemented them. A clean check over a program that cannot build is the shape of mistake I care most about, because check being quiet is supposed to mean the output works.
Fixing those two got the app to build. Then I ran the tests the generator writes, and they found the rest. Each item below is something the generated app relies on and the compiled app didn't do:
- The reset token. Rails 8's
has_secure_passwordgenerates a signed, expiring password-reset token without being asked. It's now implemented, in Rails' own wire format: a token minted by Rails 8.1 verifies in the compiled app, and one minted by the compiled app is one Rails would read. Changing the password invalidates it, as it should. - The validations.
has_secure_passwordalso validates that the confirmation matches. That wasn't implemented, so a password reset with a mismatched confirmation succeeded and changed the password. That one is fixed now, and it's the one that bothers me most. params.permitwith norequire. The generator writes@user.update(params.permit(:password, :password_confirmation)). My first fix for that turned a crash into an update that silently did nothing, which the tests caught. It now builds the hashupdateactually reads.normalizes :email_address. The generatedUserdowncases and strips the email address, on assignment and infind_by. That's what makes sign-in case-insensitive. It was ignored.- The reset mailer. It says the link expires in
distance_of_time_in_words(0, 900), passing seconds where the helper had only ever been given times. - The test helpers.
assert_no_changes,assert_enqueued_email_with, and the generator'ssign_in_as, which includes itself into the tests in a way nothing had used before.
With those in place, the generated tests pass on both CRuby and the Spinel-compiled binary: passwords 7 of 7, user 1 of 1, sessions 3 of 4. The fourth fails on a known difference: a cookie that was never set reads as "" rather than nil. Campfire's own suite still passes, 406 of 406.
None of this was exotic, and none of it is about Hifumi. Every Rails 8 app that runs the authentication generator has all of it, however the app was written. I'd been testing against Campfire, which predates the generator and wrote its own sign-in. Rails' own default code found in an afternoon what months of a hand-written app hadn't, which is the argument for testing a compiler against defaults.
What isn't true yet
- I haven't run any real Hifumi output. Everything above comes from one app I built as a stand-in. If Hifumi's agents write sign-in by hand rather than running the generator, its apps will hit different gaps from these. The next step, if there is one, is the actual corpus: Hifumi's design notes mention 29 workspaces.
root_urlwith no root route is still only a warning incheck, while the compiler refuses it. That's the same kind of hole as the filter, and the next one I'd close.- The smaller gaps: the cookie difference above;
save!ignoring an explicitly assigned id;generates_token_forin general, as opposed to the reset token; and an app whosedb/schema.rbhasn't been generated yet, which Hifumi never hits because it always runsdb:prepare. - Whether compilation helps Hifumi's users at all. On Campfire, the compiled app runs in a fraction of the memory Rails needs, which matters for what it costs to host. Whether that matters to someone who just described an app in a sentence is a question for the person who has watched them use it.
What it could become
What I'd like to see is Hifumi offering a compiled version of what it generates, optionally or by default, locally and in the Dockerfile. Looking at how Hifumi works, there are three places a compiler could fit, and they want different things.
In the verify loop: check, not compilation. roundhouse check takes a fraction of a second and reports file and line, which is the form Hifumi already hands to its fix agent when a check fails. It would sit next to route smoke, advisory. Its findings come in two kinds, and they need to go to different places. A filter naming nothing, or a root_url with no root route, is a bug in the app; that goes to the agent, as a route smoke failure does. "Roundhouse can't compile this yet" is my problem, not the app's; that goes to a ledger, and to me, and never to the agent. An agent that spends its fix attempts rewriting a working app around a compiler's gaps is worse than no check at all. Hifumi's notes record rejecting a linter on the same grounds: its errors were style, not breakage. That's the right bar, and the one this has to meet.
Not in the preview, at least at first. Hifumi rebuilds a preview container for every revision and runs it in development mode, inside an eight-minute build budget. That's the right design while someone is still iterating. Compiling takes minutes and brings a compiler toolchain into the image, and buys little while the app is still changing. If a compiled preview ever earns a place, it's as a separate action on a revision you've accepted.
At export and deploy: a second Dockerfile. This is where it fits naturally. The repository you take away keeps the Dockerfile that rails new wrote. Beside it would sit a second one that compiles the app in a build stage and ships just the binary and SQLite, plus a local bin/compile that does the same without Docker. Roundhouse already builds this kind of image for Campfire, so this is packaging, not invention. Kamal deploys either. It's offered only when check reports no errors, so "optional" stays honest, and the Rails repository remains the thing you own.
Roundhouse also translates an app's own tests, so the export could run them twice, once against Rails and once against the compiled app. A difference there is a bug in Roundhouse, and the report should come to me, not land on the person who asked for an app.
Should we do this?
That's a question for Paweł Strzałkowski more than for me. He built Hifumi, and he knows what its users run into. The questions I'd like answered:
- Would you share a sample of generated apps, or let me run
checkacross the existing workspaces? That would replace my one stand-in app with evidence. - Do your agents run
bin/rails g authentication, or write sign-in by hand? That decides whether the fixes above matter for Hifumi's apps or only for everyone else's. - Is a static check something your verify loop would want, and under what conditions? Advisory like route smoke, or not at all?
- Would a compiled image at export be useful to the people taking Hifumi's apps away, or is the Rails repository all they want?
- Which failures do your users actually hit after generation? Those are the ones worth fixing first, on either side.
And for anyone else generating Rails apps, by agent or by template: would you run a check that tells you, before anyone opens the page, that a filter names a method nothing defines?
Paweł and I will both be at Deccan Queen on Rails in Pune next week. If you're there too, find either of us.
Roundhouse is open source: dual-licensed MIT / Apache-2.0. The changes described here are commits 65cc85c1 and 16ebe6a9; the Rails 8 authentication generator's output, overlaid on a blog and run, is pinned in tests/rails8_authentication.rs.