The job finished. Did the export?
Suppose a customer requests a CSV export. Two days later, they write to support: there is no download. Support searches the logs for the export’s job and finds its final attempt:
Stopped retrying BuildExportJob
(Job ID: ec0543aa-…)
due to a ExportUnavailable (export-44),
which reoccurred on 2 attempts.
Performed BuildExportJob
(Job ID: ec0543aa-…)
from SolidQueue(default) in 0.1ms
These lines show the reproduced log sequence from the Active Job probe, with the job ID shortened and long lines wrapped for display.
The first line reports a failure. The second line reports the ordinary end of an attempt. Solid Queue marked that attempt’s job row finished,…
Frequently Played 🔗
I tend to listen to the same songs or albums on repeat that are evocative of how I’m feeling or what’s going on with me. Here is what I’m currently listening to over, and over, and over, and over, again.
Parasite 🔗
Still waiting for the vinyl release to be delivered.
Full Lyrics
Yes, now I need you
Yes, you are mine
Yes, you have shown me
Yes, you define
Larger Than Life 🔗
The new live album is well worth a spin.
Recently I was pairing with someone. They were currently driving, and asked for my opinion on a decision. I said something that was dangerous. Words that may have sounded abrupt. Words that could paralyze a pairing session. Words that could show an unfixable difference of opinion. The words I said were:
I don’t care.
Care To Explain 🔗
My pair and I were wrangling some logic in a controller. We decomposed what was a long public action into smaller private methods. We discovered we could bundle a few of these methods up into a domain concept. We thought of creating a class for these. We even had a name for it! Then my pair asked the 🔥burning🔥 question that prompted my uncaring response:
Where…
On Windows, a maliciously crafted tarball can
write files outside of the extraction directory.
## Impact
Path traversal (CWE-22). A crafted tar archive can cause files to
be created or overwritten outside the extraction directory.
Applications and scripts running on Windows Ruby (platforms where
File::ALT_SEPARATOR is "\\", such as mswin and mingw) that extract
tar archives from sources they don't fully control, using
Minitar.unpack, Minitar::Input#extract_entry, or Minitar::Input#each.
Platforms where File::ALT_SEPARATOR is nil, including Linux, macOS,
and the BSDs, are not affected, because \ is not a separator there
and is extracted as a literal filename character.
A crafted archive…
A Ruby object-document mapper does not apply its automatic field-level
encryption configuration to certain fields when the data model uses
class inheritance. Values the application designated for encryption
are written to the database in readable form without any error or
warning. A database account holding only read access to the
application's collections, and no access to the encryption
key store, may obtain those values.
## Impact
An attacker on the network path between pumactl and a Puma control
server reached over ssl:// can steal the control token and use it
to stop, restart, or inspect Puma.
pumactl support for ssl:// control URLs was added in v4.3.0 (#2052).
When pumactl connects to an ssl:// control URL, it accepts any TLS
certificate. It should check that a trusted authority issued the
certificate and that the certificate matches the hostname in the URL.
An attacker who can intercept or redirect the connection can present
their own certificate, and pumactl then sends them the control token.
With the token, the attacker can send authenticated commands to the
real control server, including stop,…
## Impact
Puma servers running in single mode with the default force_shutdown_after
:forever can be prevented from shutting down or restarting by an
unauthenticated client.
When Puma begins a graceful shutdown, it hands any request that is
still being received to the thread pool so that it can finish. The
thread pool then waits for that request with no deadline. Puma's
read timeout restarts every time a byte arrives, so an attacker who
sends a POST with a large Content-Length and then trickles one body
byte every few seconds can keep that request "in progress" for as
long as they like. The same works with chunked bodies and with
trickled headers. Puma stops accepting new connections, but…
## Impact
An unauthenticated client can stop Puma from closing other connections
when their first_data_timeout or persistent_timeout expires.
The reactor keeps waiting connections in a queue sorted by deadline.
Puma sorted the queue only when new connections arrived, but it
extended a connection's deadline every time that connection sent
data. A client that connects and then sends about one byte per
timeout interval, without completing its request, stays at the
front of the queue with a deadline that is always in the future.
Every expired connection behind it stays open for as long as the
client keeps sending, or until a new connection arrives and causes a sort.
Connections held this way…
## Impact
Puma servers that set an ssl_cipher_filter containing + accept TLS
cipher suites that the filter is meant to exclude. Any client that
can reach the SSL listener can negotiate those suites.
Support for ssl_cipher_filter in ssl:// bind URLs was added in
v3.12.0 (#1478), and in the ssl_bind DSL in v4.0.0 (#1676).
In an OpenSSL cipher string, + means "both": AES+SHA selects suites
that are AES and SHA. Puma decodes the query of a bind URL as HTML
form data, where + means a space, and a space in a cipher string
means "either". So the filter !aNULL:AES+SHA, which is the example
in Puma's README, reaches OpenSSL as !aNULL:AES SHA: every AES
suite plus every SHA suite. On OpenSSL 3.x…
## Impact
An unauthenticated client can make Puma:
1. write log lines as fast as it can...
2. ...which eventually may fill the disk...
3. ...and when the disk fills, Puma will stop accepting connections.
The client can do this by opening enough idle connections to use up
the process's file descriptors.
When accept fails because the process is out of file descriptors
(EMFILE), the connection stays in the listen backlog, so the listening
socket stays readable. Puma logs the error and calls accept again
at once, with no pause. Each attempt fails the same way and writes
another "Listen loop" error line. An attacker who opens about as
many idle connections as the file descriptor limit, and…
## Impact
An unauthenticated client can hold a Puma worker thread indefinitely
when Puma sends it a TLS response, or a file response larger than
64 KB. With enough connections, it can take the server offline.
The client sends a request and then stops reading the response.
Puma's write timeout (10 seconds) should then free the worker
thread. In two cases it never fires, and the thread stays blocked
until the client disconnects:
* TLS: MiniSSL::Socket#write_nonblock performs a blocking write.
This has been the case since v3.9.0 (#1274).
* File bodies over 64 KB, on any bind: Puma sends File bodies, and
bodies that respond to to_path, with IO.copy_stream, which has no
write timeout.…
## Impact
An unauthenticated client with one connection per worker process can
stop Puma from serving every connection that waits in the reactor,
for as long as it holds that connection open.
Each Puma worker has one reactor thread, which holds connections
waiting for more data: keep-alive connections between requests,
requests that arrive in more than one read, and TLS connections
still in the handshake. When a request with Expect: 100-continue
completes its headers on the reactor thread, Puma writes the
HTTP/1.1 100 Continue interim response with a blocking write and
no timeout. An attacker pipelines a request for a large response
before the Expect: 100-continue request and does not…
With hybrid cryptography, an attacker who obtains the encryption key can use
it to decrypt data (acting as symmetric encryption rather than asymmetric).
A malicious or compromised memcached server or intermediary can exhaust
Dalli client resources or keep a TLS operation blocked beyond the
configured `socket_timeout`.
Pipelined `get_multi` can accumulate a header with no terminating CRLF
without the 8 KiB limit used for ordinary reply lines. A declared value
length can cause an excessive read, up to memcached's 1 GiB item maximum,
even when the default 1 MiB `value_max_bytes` limits writes. Stats and
quiet or bulk operations accept an unbounded number of complete replies
while waiting for their terminator. On Ruby 3.3 with its default OpenSSL
gem, blocking TLS handshake and reply reads can exceed `socket_timeout`.
The attacker must…
Alchemy::Admin::ElementsController' permitted nested ingredient
attributes with an open hash (ingredients_attributes: {}), and
Alchemy::Element accepted them for every ingredient column. A
user allowed to edit elements, which in the default role set
includes author, the lowest content role, could send a modified
PATCH /admin/elements/:id request and:
* change an ingredient's STI type, for example turn a Text ingredient
into an Html ingredient, whose view renders its value unescaped;
* point an ingredient's polymorphic related_object at a record of
any model in the application;
* create ingredients of any type and role that the element's
definition does not declare;
* replace an…
Since 8.4.0, Alchemy::BaseController loads the current user in a
before_action that runs before the CSRF check. With the default
null_session forgery strategy, a forged cross-site request still
runs as the signed-in user in apps whose current_user_method memoizes
the user (for example the Rails 8 authentication generator or
@current_user ||= ...). An attacker can make a signed-in editor or
admin change content in the Alchemy admin by getting them to
visit a malicious page.
## Impact
Cross-site request forgery against the Alchemy admin. An attacker who
gets a signed-in Alchemy user to visit a malicious page can perform
any state-changing admin action that user is allowed to do:…
The admin layout wrote the value of the alchemy-elements-window-width
cookie into the style attribute of the admin without checking it:
<%= content_tag :body, id: 'alchemy',
class: alchemy_body_class,
style: cookies["alchemy-elements-window-width"] &&
"--elements-window-width: #{cookies["alchemy-elements-window-width"]}px" do %>
The admin JavaScript sets this cookie to the width the user dragged
the elements window to. A cookie value like 1; background:
url(//attacker.example) ends the custom property and adds further
CSS declarations to the body's inline style.
## Impact
Rails escapes the attribute value, so the cookie cannot add HTML or
script, only CSS declarations on the …
Alchemy::Node validated its url with a pattern that accepted any URI
scheme, and the linkable ingredients (Link, Text, Picture) never
validated their url at all. A user with permission to edit menus or
element content could therefore store a javascript: url, which the
menu template and the ingredient view components render as the href
of an
element. Activating such a link executes the stored script
in the site's origin, with the privileges of whoever clicked it.
Added chart, slider, resize, and sounds components, and improved 13 existing components, including a color picker rebuilt without Shoelace and swipeable toasts.
Components in this release:
View full changelog →
Direct link to podcast audio file
The biggest news of the week isn't Jensen Huang, or the Office365 outage, or the FAA grounding all air traffic.
Nope, it's the Apple HomeView. Bet you didn't see that coming.
As always, you can tell me I'm wrong by e-mailing to podcast@searls.co or via the feedback form. If you bother reading show notes but can't be bothered to write me an e-mail, you're no friend of mine.
I am filled with regret that I keep cramming so many links into each episode:
Marco Roth, independent consultant and the creator of Herb and ReActionView, joins Robby to talk about teaching Rails to understand its own templates.
Marco explains how Herb parses HTML and ERB together, why that lets it catch broken views long before they reach production, and how it is set to become part of Rails 8.2. They also get into how ReActionView makes Rails views reactive without rewriting them, the story behind RubyEvents.org, and why tooling like Herb matters even more when AI agents are writing your templates.
Listen to this episode (90m) or subscribe to On Rails in your podcast player.
Solid Queue's concurrency limits are built on a semaphore stored in a database row. Blocked jobs wait as stored state, and permits can expire. This post traces how it works in version 1.4.0 and what expiry changes.
Hi, Wojtek here. Let’s explore this week’s changes with another big round of fixes in the Rails codebase.
Add a guide for using Hotwire with Rails
The guide can already be read on the Edge Guides.
Use an Array for storing Active Record attributes
Cast and freeze a model’s attribute defaults when its schema is loaded.
Deprecate unscope(where: :column) removing conditions on other tables
An unqualified column passed to unscope(where: ...) currently removes conditions on every table that has a column of that name, including joined and eager loaded tables. In Rails 9.0 it will only remove conditions on the relation’s own table.
Detect Kamal in app:update command
The update command didn’t…
When AI Agents Go Rogue
Security vulnerabilities, AI-powered coding, and a whole lot of pull requests take center stage this week on Remote Ruby! Chris, Andrew, and David kick things off with some alarming security exploits, including a critical Active Storage vulnerability and the growing risks of AI-driven attacks. They explore how Hot Cell could make Rails file uploads safer, share frustrations over security requirements for AI plugin submissions, and discuss practical ways to make AI-assisted development more manageable. Andrew reveals his new five-file rule for creating smaller, easier-to-review pull requests, while Chris considers how AI could fundamentally change software development and Rails releases.…
I’m a big believer in what Rich Hickey calls Hammock-driven
development, a bit of
tongue-in-cheek term for solving problems by spending a lot of time thinking
about them. Rich talks about the importance of taking the time to think a
problem through, instead of rushing to the computer to type out code, and he’s
right to note that a lot of the big problems with software are problems of
misconception, which is really about thinking.
In my work and in my life I have seen time and again how a lack of thinking
things through before acting can lead to bad results. The Hebrew saying “סוֹף
מַעֲשֶׂה בְּמַחֲשָׁבָה תְּחִלָּה”, meaning “the end of a deed is in its initial thought”, is
exactly about…
An AI agent runs the test suite and lints in its local checkout. Then, you open a pull request and GitHub Actions runs the same checks against the same commit. This is wasteful, so I built already-tested to submit agent claims with PRs and skip CI workflows. This is also called “shift left”.

Of course, anyone who can edit the pull-request description can forge a claim. It is not a cryptographic attestation, a test result signed by the machine, or evidence that the tests ran. Don’t ever disable CI on main.
Install
Setup takes two project changes: add the gate to the GitHub Actions workflow, then add this to AGENTS.md or .github/copilot-instructions.md:
## Local CI Claims
- Install `al…
A release needs to be reliable, which means it needs to be testable.
A typical release selects a version, updates a changelog, builds an artifact, creates a commit and a tag, publishes the package, creates a GitHub Release, and often prepares the next development version. Projects add their own steps, such as updating version references in documentation, and these steps need to agree on what was released.
If one breaks, the release fails halfway through, leaving a published package without a matching tag or a development version that wasn’t updated. You can’t always undo a published package and start over.
Test your release code.
Releases Are Software
I’ve spent plenty of time…
Today, I talk about the estimated date for the pre-sale and how learning Fusion 360 is hard.
You’ve heard it this year. Maybe you’ve said it yourself: “You don’t need frameworks anymore. Just let the agent write it. Why would you depend on somebody else’s gem?”
And it’s spreading. My friend Andrzej Krzywda goes to every Ruby conference and talks to everybody, and a few weeks ago he wrote that 40% of the devs he meets are trying to reinvent RubyLLM, my AI framework. One person’s impression, sure. But you can feel it in the air.
I think it’s bullshit, and I wanted to prove it, not just say it.
Same Agent, Same App, Four Times
So I took a coding agent, Codex on GPT-6 Astra, a really good one, and gave it the same app to build four times: with RubyLLM on Rails, with LangChain and…
How we pair AI agents with our knowledge of Shopify’s systems to safely attack our production infrastructure, find bugs, and reduce risks.
#826 — November 12, 2026
Read on the Web
Ruby Weekly
Metaprogramming on a Budget — A "peek behind the curtain of Rails' metaprogramming magic!" Active Record generates 21 methods for every column, which could eat a lot of memory, but a little define_method trick allows methods to share compiled instruction sequences…
Hartley McGuire (Rails at Scale)
💡 The follow-up Hartley mentions at the end has just been merged: Aaron Patterson's PR stores Active Record attributes in an array so reader methods can be shared by column position, with the reading of attributes getting up to 1.3x faster.
Leaving New Relic? No…
This is a story about a donate button that slowly turned into a switchboard operator, not because the payments were hard, but because two SDKs refused to speak the same language. For the longest time, our donate button had exactly one job. A donor picked an amount, tapped Donate, a payment sheet slid up, money …
Continue reading When Razorpay Meets Stripe: An Adapter Pattern StoryA dropdown looks like a problem solved by many already. But it is not too difficult to build your own and allows to make them exactly how you want them to be. It has to open next to its trigger, flip when there’s no room, stay inside the viewport, move focus into the panel, cycle through its items with the arrow keys, close on Escape and disappear when you click somewhere else.
Then there’s the part I find most useful: the same panel should also work as a right-click context menu, opening where the pointer is instead on a button.
That’s a handful of behaviors together. I keep them in one custom element, <ui-dropdown>.

The click version is a trigger plus a panel marked with data-dropdown-pa…
William Mena shares how Rails World changed his daily workflow and what he’s not changing yet.
Continue Reading
A full year since my last programming post, recharged by months of being offline LARPing as a normal person, I’m ready to make my grand re-entrance to the Internet.
Ready for it?
Today I’ll walk you through adding a small feature to a little Ruby gem I made a few years ago.
Audience: 【・_・?】
I’m pretty sure I’m the only person who uses my gem.
Audience: ( ̄(エ) ̄)ゞ
Oh, and we’ll do it entirely by hand. No AI!
Audience: (ง ˃ ᗜ ˂)و ̑̑ ヾ(。★ω★。)シ ٩(ˊᗜˋ*)و ♡ ₍₍⚞(˶˃ ᵕ ˂˶)⚟⁾⁾
(At least, that’s how I feel these days…
Authors: Aibek Izhanov, Backend Engineer, and Travis Turner, Tech Editor
Topics: AI, Rails, Case Study, Ruby, LLMs

How Evil Martians moved Uscreen’s MCP server into its Rails app as a remote, multi-tenant service: Doorkeeper OAuth, tenant-scoped tools, and dry-run previews for destructive operations.
Uscreen, the leading all-in-one membership platform for video creators and entrepreneurs, came to Evil Martians with a working Model Context Protocol (MCP) server already tested by early adopters. To use it, a creator installed an NPM package that ran the server on their computer, entered an API key, and plugged it into the AI client of their choice. It worked, but it took a lot of setup. So, to…
How we train and serve a shared generative model for personalized search and recommendation at Shopify scale.
If AI is writing all the code and no one is reviewing it, how the heck can anyone be responsible or accountable for code they not only don’t understand and eventually can never understand?
AI's primary purpose is to deskill its operators.
The short answer is that programmers become responsible for outcomes, not code. The longer answer is there are no programmers - AI is a
replacement, not an assistant, since it’s primary purpose is to deskill its operators.
If AI is writing all the code and no one is reviewing it, how the heck can anyone be responsible or accountable for code they not only don't understand and eventually *can never understand*?
The short answer is that programmers…
I feel like AI-related blog posts should all begin with a disclosure as to how cooked the author is. On a scale of 1 to 10, I'm apparently now at "built a custom software factory and has fooled himself into thinking it actually works." So I guess that makes me an 8 or a 9? Not full-blown psychosis, I hope, but consider yourself duly warned.
My agents are currently shipping 20 to 40 issues a day across the dozen or so iOS apps I'm building. Today, I'm going to show you a bit of my app, MeatProxy, and how it governs my software development process. I'm not going to let you download it (you don't want it, trust me), but at the end of this post, your agent will find instructions for building…
An engineering manager I worked with before asked me how I can deliver good software for lower cost.
Am I slow? Is it truly good? Am I losing money?
One should always keep such friends close.
And it’s a fair question.
The old rule in software is good, fast, cheap: pick two. But I provide all three.
The short answer is that I changed how I work.
The long answer follows.
I jumped in advance
For most of my career I had it all: a reliable full-time salary as a staff engineer,
solving tricky engineering or team problems with people I like.
Then AI agents appeared, and got better at writing code faster than anyone expected.
And I wanted to build software differently.
How long would jobs…
If you run a small business, there’s a good chance your website, your proposals, and the handouts you give clients were made at different times, by different people, in different tools. Each one looks fine on its own. Put them side by side, though, and they don’t quite look like they came from the same company.
I ran into this recently on a side project: setting up the brand and website for a new business that had little more than a few rough documents to its name. I used Claude Design, Anthropic’s design tool, and instead of starting with the website, I started with a design guide (also called a style guide or design book). That’s one document that records the business’s colors, fonts,…
What does it take to make an open source project welcoming to contributors—and manageable for its maintainers—in the age of AI? Rachael Wright-Munn joins David Hill to share what she’s learning through her work on Ruby Events, where supporting new contributors and reviewing AI-assisted pull requests raise many of the same questions about context, documentation, and code quality.
Rachael explains how Ruby Events uses YAML validation to catch problems and provide actionable feedback, along with an evolving Active Record–style interface built around Yerb. The conversation explores how clear rules, useful abstractions, and better development environments can help contributors produce…
Many of the Ruby on Rails apps we work on now have an AI layer of some kind. Sometimes it’s a single RubyLLM chat in a background job, sometimes it’s an MCP client wired into a support tool, and on most teams there’s a coding agent running in somebody’s editor. That layer gets the same security process as the rest of the app, which usually means bundler-audit in CI and a Ruby upgrade when somebody gets to it.
The last few weeks produced four separate problems in that layer. None of them has an advisory against a Ruby gem, so a dependency scanner stays green for all four. Two come from reading the source of ruby_llm-mcp, a community MCP client gem, one is a version constraint, and one is a…
RubyLLM 2.2 is out! I released it on stage at SF Ruby, and it’s the release I’m proudest of.
The code a model writes now runs in a sandbox on your own servers, and skills and file stores build on it. Tools can ask a person and wait for the answer. A chat survives a deploy. And there are context budgets, tests without a provider, and four new providers.
It’s also the first release I built for a new kind of user: the coding agent. More and more of the code that uses RubyLLM is written by Claude Code, Codex and Cursor, so I spent as much time making RubyLLM easy for them to use as I did on features. The talk was about that, and about the idea that you don’t need frameworks anymore. I wrote…
We are using the proprietary hosted version of OpenAI whisper-1 to transcribe audio, with timestamps, into timestamped VTT.
The newer gpt-transcribe may reportedly do a better job of transcribing — but it doesn’t provide timestamps. One could probably try to use other tools to then sync the transcript, but we haven’t looked into that, not sure if it would get you anything useful, we’re trying to keep it simple.
The platform-hosted whisper-1 has a limit of 25 MB per audio file input, but no duration limit.
So when we started, when I was new to such things, I figured, great, we can downsample our oral history interview content to 16kbps mono Opus/ogg audio, and fit a lot in 25MB.…
I recently built a Markdown linter with GitHub Copilot CLI. In between, I reviewed other projects, edited this blog, and generally treated one conversation as a terminal into which I could dump whatever I was doing next. I really loved the fact that Copilot would context switch, do some work, then go back to what it was doing. It’s so smart!
Well, that may have burnt some extra tokens.
Counting Tokens
The local usage logs for the roughly three-and-a-half-day development window showed approximately 4,322 model calls, including delegated agents:
Token Type
Millions
Non-cached input
11.4
Output
2.4
Cache…
Back in August, we tested Anthropic’s promise to watermark Claude’s output and found nothing there yet. Since then, Anthropic has published which models it marks and how, and it’s no longer alone: OpenAI has announced a text watermark of its own, Google has opened its detector to the public, and xAI has taken a different route entirely.
Since August, though, the questions we’ve heard most are about what comes after the watermark: who owns a logo that a model drew, whether code written with AI can be treated as proprietary, and whether a prompt counts as authorship. And behind those sits a newer one: if your AI-assisted marketing turns out to infringe someone’s work, does your insurance…
What changed when agents started writing our mobile code, and how we redesigned our architecture, tooling, and workflow around validation.
It’s been a week of non-stop work.
First, big thing: I merged diagnostics into Hanami Webconsole! With that done, I was able to do a review of the rest of the gem and push up a range of improvements, along with a related change in Hanami Reloader.
I also got to sight some of the design revisions that Max has been putting together for this. It’s going to feel truly us when it ships. Can’t wait.
I shipped the new, styled output for Hanami CLI! It feels so much more polished now, and this is just the beginning. Also discovered that fast terminals are fast and pushed up a fix for a flickering spinner.
Katafrakt Korner: Paweł found and fixed a gap in our error handling around actions…

Version 6.2.2 of the Passenger application server has been released. This release is mostly focused on bug-fixes and security hardening.
Other Improvements
- Harden against symlink attacks on improperly secured instance directories. This is a mitigation, and not intended to replace the use of proper filesystem permissions.
- Harden against secure headers containing header delimiters.
- [Enterprise] Fix the hard memory limit not being enforced on a process that already exceeded the soft memory limit and is still finishing its requests.
- [Standalone] Fix Passengerfile.json strings that contain \uXXXX escapes for non-ASCII characters being garbled or making Passenger crash.
Upgraded libraries
If you generate an empty model class in a Rails application, the attribute
methods are automatically available:
class Post < ActiveRecord::Base
end
p = Post.new
p.title = "Metaprogramming on a Budget"
p.title # => "Metaprogramming on a Budget"
It’s one of Rails’ oldest features, as seen in the old Build a Blog in 15
Minutes demo from 2005. But how does it work?
✨ Metaprogramming ✨
If you’ve never used it before, metaprogramming may sound really scary, but its
not too bad in practice. All it really means is that we’re writing code that
writes more code.
To get a better understanding, let’s try implementing a simple version of this
Rails feature ourselves. The entrypoint will be a…
In the previous post, we saw how a large language model like Claude works. Your application can use Claude by sending HTTP requests to the Claude API, which Anthropic runs. The model can't query your database, call your APIs, or run any of your code. All it can do is read the text you send it and write text back. Yet you've used AI features that look up records, search the web, or edit your code in a local repo. They're all built on a feature of the Claude API called tool use. With tool use, Claude doesn't run your code. It asks your app to run it, and your app decides whether to run it or not.
In this post, we'll build that tool exchange to learn what goes back and forth between…
I’ve been using git worktrees a lot lately. Having a couple of branches checked out next to each other (often with an agent working away in one of them) is awesome. Until two of them start running migrations against the same development database and everything gets mixed up.
The obvious fix is to give every worktree its own database. But I like to develop against an anonymized copy of production, and copying gigabytes of data for every new worktree is slow and fills up my disk in no time.
Postgres 18 quietly shipped a new setting that fixes both: file_copy_method = clone. It’s been there since 18.0 (changelog), so if you’re still on an older version, that’s a good reason to upgrade.
What…
The next North West Ruby User Group in Manchester, UK is in a fortnight on 19 November at Colony, One Silk Street. Sam Péchèr will reflect on starting out as a junior last year, just as the industry started to shift to not writing code.
We’ll be at the venue for pizza from 6:30pm with the talk starting at 7pm. Afterwards we’ll retire to The Crown & Kettle for a drink. The venue, pizza, and drinks are kindly provided by NWRUG’s sponsors Fatsoma and thoughtbot.
Register here if you would like to attend. Full details on this (and all NWRUG events) over on NWRUG’s website.
If you enjoyed this post, you might also like:
On the 4th of November the Rails security team shipped
8.0.5.2 and 8.1.4.1,
and it is a big one: 21 security issues in a single release. One of them is
rated critical,
GHSA-f3w6-w445-fwh9,
“Possible arbitrary file write and read in Active Storage image analysis”, with
a CVSS score of 9.8.
Recently I wrote about an idea that’s been
floating around in my mind for a long time - the ability to do metaprogramming
in Ruby by manipulating Ruby ASTs, similar to the macro capabilities of
languages such as Lisp or Elixir. The use of eval (in its different
variations) is a common metaprogramming technique in Ruby that consists of
generating code at runtime from dynamically constructed strings. This is often
done for performance reasons: ERB compiles templates into optimized code by
generating pieces of Ruby source code then putting them together and calling
eval; In Rails, the same technique is used for implementing features such as
associations, or scaffolding. With eval, we can avoid…
Releasing Ruby gems manually typically involves incrementing a version in a file, creating a tag, pushing a gem to Rubygems and making a GitHub release. These tasks can be automated in many ways, including release-gem or GHA with rubygems/configure-rubygems-credentials and trusted publishing. Moreover, you can build upon these approaches when you need something beyond the plain rake release, such as updating a CHANGELOG.md, by rolling miles of brittle GitHub Actions YAML and shell script AI slop.
As an alternative to both the manual process and scripts, I’ve been asking AI to follow a hand-crafted RELEASING.md. AI can run tests, date CHANGELOG.md, ask me for a RubyGems OTP, make the…
Why Kamal 2 fails docker login with an empty registry password when .kamal/secrets tries to source .env, and three fixes that work.
My father is a poetry translator, so perhaps my obsession with prose runs in the family. This started before Markdown: I wrote rubocop-exception_messages after noticing inconsistent exception messages in Grape.
After I replaced this blog’s Python spell checker with a RuboCop plugin I still had a Node installation to check Markdown. Since it’s a Jekyll blog, and the build is Ruby, the spelling checker is Ruby, and my default set of code examples are also Ruby, why was formatting the README Node.js?
I first tried extending rubocop-md, proposing prose checks and parser diagnostics, a reusable Markdown parser, hidden cop-directive fixes, front-matter handling, and preserving prose alongside…
Hi, it’s Greg, bringing you the latest changes in the Rails codebase. It was a busy week, with a lot of changes, so buckle up!
Rails 8.0.5.2 and 8.1.4.1 have been released!
These are security releases, addressing 21 security issues, so you should upgrade if you haven’t already.
Allow anonymous subclasses of cache stores with LocalCache
A recent change built the local cache’s registry key from the store’s class name in initialize, and that name is nil for an anonymous class, so Class.new(ActiveSupport::Cache::MemoryStore).new blew up with a NoMethodError. Anonymous stores are handy in tests to record what a request writes to the cache, and they work again.
Fix Active Record includes with …
Garrett and I recorded episode two of The Friday Deploy. We started with personalized interfaces and ended with a basketball app and me casually mentioning that I’d rewritten a chunk of Flare to run on Cloudflare. As one does.
But the thing I keep coming back to is the pull request that makes your stomach hurt.
I’ve made plenty of those lately. Ask AI to build something. Have another model review it. Fix the findings. Review it again. Find more things. Fix those. Pretty soon the little change you wanted is 3,000 lines and you don’t want to merge any of it.
For a while, my response was to keep going. Surely one more review would make me feel better.
Sometimes the better answer is to throw…
So who is going to buy Twitter now that he’s throwing his toys?
Vulnerabilites & AI with Nate Berkopec
Nate Berkopec returns to Remote Ruby joining Chris, Andrew, and David for a deep conversation about the latest wave of Ruby security vulnerabilities, AI-powered cybersecurity, and the rapidly changing role of software engineers. Nate shares his firsthand experience investigating a serious Ruby regex vulnerability, explains why developers need to take a more proactive approach to security, and challenges traditional development practices in an AI-driven world. From autonomous coding agents and dramatically faster development workflows to rethinking unit tests and code reviews, this episode explores why the future of programming may be less about writing code and more about building systems…
Code Got Cheap. The Rest Didn't. November 6, 2026 AI has fundamentally changed how we build software. But implementation is only one part of the job. Architecture, business knowledge, testing, and understanding real user workflows still require experienced developers. By Guido Boyke I've been developing software for more than 30 years, including about 20 years … Continue reading Code Got Cheap. The Rest Didn’t. →
Lately, I've been trying to learn more about AI, particularly large language models (LLMs). My usual approach to learning a new subject is to read the classic books on the topic. However, since this version of generative and agentic AI is so new and evolving so rapidly, it's been difficult to find good, established books that covers it and explains it so a web developer like me could understand it.
So I decided to try something different by learning AI with AI. That is, I asked Claude to explain how LLMs work over a series of back-and-forth conversations over a few days to build my understanding of the topic. This post is my attempt to explain what I've learned about how LLMs…
I'm a web…
Our architecture lets agents test mobile app behavior without an emulator and use one test suite to keep iOS and Android in sync.
The JRuby community is pleased to announce the release of JRuby 10.0.8.0.
JRuby 10.0.x targets Ruby 3.4 compatibility.
Thank you to our contributors this release, you help keep JRuby moving forward! This cycle we had several new contributors and hundreds of pull requests. Keep it coming, JRubyists! @aminmansuri, @kares, @hiukkanen, @evaniainbrooks, @stefanlew-tractive, @sampokuokkanen, @zev, @matrei, @notsrch, @sferik, @RaphaelFakhri
Notable Changes
Security
- The asm bytecode library was updated to 9.11.1 to fix recent CVEs (none of which affect JRuby). #9858
Standard Library
The JRuby community is pleased to announce the release of JRuby 10.1.3.0.
JRuby 10.1.x targets Ruby 4.0 compatibility.
Thank you to our contributors this release, you help keep JRuby moving forward! This cycle we had several new contributors and hundreds of pull requests. This list includes contributors to JRuby 10.0.8.0. Keep it coming, JRubyists! @samuel-williams-shopify, @aminmansuri, @kares, @hiukkanen, @evaniainbrooks, @stefanlew-tractive, @sampokuokkanen, @edipofederle, @samyron, @reginaldalfret, @albertalef, @taxor03, @zev, @matrei, @notsrch,
@sferik, @RaphaelFakhri
Notable Changes
See also the JRub…
RubyMine 2026.3 is out!
This release brings JetBrains Air for working with your preferred coding agents, native access to WSL and Dev Container projects, and smarter Ruby and Rails support. You’ll also find easier interpreter setup, improved navigation and refactoring, and updates that make everyday development more comfortable.
Download RubyMine 2026.3 from our website or update via the Toolbox App.
Now, let’s explore the highlights.
JetBrains Air
I have always been interested in frontend solutions for building web
applications. Even though I’m more of a backend programmer, I really dig all the
different techniques people come up with for creating browser-based apps that
are both reactive (reflecting remote state in real-time) and interactive
(responding to user actions).
Over the last few years, I’ve been looking at different frontend frameworks and
libraries, to see how they work, and what I can learn from them. I adore
Svelte, which is really smart and efficient. Its sister
framework SvelteKit inspired the
file-based routing in my own web framework for Ruby,
Syntropy. I also really like the
approach of the Phoenix framework…
A story of race conditions, Docker containers, RabbitMQ queues, and the architecture decisions that kept our risk management platform alive under pressure. The Problem Nobody Wants on a Trading Floor Picture this: it’s 9:15 AM. The market just opened. Orders are flooding in — portfolio checks, risk calculations, compliance validations. And your Risk Management system? …
Continue reading From One to Five: How We Solved a Real-Time Trading System’s Scaling CrisisAsk any LLM to style a layout and it uses CSS units like: px, rem, em, % and vh. So do you, out of habit. But CSS has grown its number of units. Ones that fit specific jobs way better than your old defaults. Here are seven your code generator has probably never suggested.
They are all baseline ready (e.g. supported by core browsers for at least 30 months), so there is no fallback excuse left. Let’s get into it.
Logical properties: think in axes (x and y)
A few of these units only make sense when you drop the ideas of “width” and “height” and think in axes instead. CSS now measures along the inline axis (the direction your text flows, left to right in, for example, English) and the block…
RubyGems 4.0.24 includes enhancements, bug fixes and documentation and Bundler 4.0.24 includes enhancements and bug fixes.
To update to the latest RubyGems you can run:
gem update --system [--pre]
To update to the latest Bundler you can run:
gem install bundler [--pre]
bundle update --bundler=4.0.24
RubyGems Release Notes
Enhancements:
- Installs bundler 4.0.24 as a default gem.
Bug fixes:
- Skip executable removal gracefully when the bin directory is missing. Pull request #9963 by Evan Li and Hiroshi SHIBATA
- Fix file locks letting multiple processes in when lock files are removed. Pull request #9930 by Richard Janita and Hiroshi SHIBATA
- Turn off echo before printing the…
Documentation:
- [DOC] Fix link. Pull request #10014 by Burdette Lamar
Bundler Release Notes
Enhancements:
- Read the Ruby version from mise’s inline table form. Pull request #9991 by…
Author: Varya Nekhina, Account Manager
Topics: Case Study, AI, DX, Design for devtools, UI coding harness, Agentic development, Figma, React, Ruby on Rails, CSS, Accessibility

Buildkite runs CI/CD for some of the most demanding engineering teams in the world. Why a team of opinionated developers hired Evil Martians to build a framework-agnostic design system with AI skills that guide coding agents through every component.
Buildkite is a high-scale, hybrid CI/CD platform that lets engineering teams run fast automated build, test, and deployment pipelines.
If you've set up an automated email sequence and haven't reviewed it in a while, here's how to audit it with help from AI agents.
Continue Reading
.bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
.bh__table_cell { padding: 5px; background-color: #FFFFFF; }
.bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
.bh__table_header { padding: 5px; background-color:#F1F1F1; }
.bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
Welcome to the 40th edition of Ruby AI News! This edition features an avalanche of security issues, Ruby’s MCP SDK reaching Tier 1 status, a closer look at building AI evals for Rails, and much more.
Read on the web
Personal…
.bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
.bh__table_cell { padding: 5px; background-color: #FFFFFF; }
.bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
.bh__table_header { padding: 5px; background-color:#F1F1F1; }
.bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
New Sponsorship Opportunities
Ruby AI News has reached its 40th edition, and to mark it I am opening sponsorship packages that cover everything in the RoboRuby network. It is built for organizations that want in depth…
### Impact
Since Rails 8.1.0, `ActionDispatch::RemoteIp` derives `request.remote_ip` from
`Rack::Request#forwarded_for`, which under Rack 3 reads the RFC 7239
`Forwarded` header in preference to `X-Forwarded-For`. When a reverse proxy
appends the client address to `X-Forwarded-For` but passes a client-supplied
`Forwarded` header through unchanged, `request.remote_ip` returns an address
chosen by the client, and the IP spoofing check does not raise, because it
compares only `Client-Ip` with the forwarded addresses. An unauthenticated
attacker could therefore spoof `request.remote_ip`, possibly bypassing
IP-based access controls and rate limits keyed on the client address
(including the…
### Impact
With the HTML5 sanitizer (`Rails::HTML5::Sanitizer`, the default vendor on
CRuby for applications using `load_defaults 7.1` or later), Nokogiri before
1.19.5 serializes `<` and `>` unescaped inside attribute values. The Action
View text helpers `highlight` and `simple_format` sanitize their input and
then process the resulting HTML as a string: `highlight` splits it on tag
boundaries to insert matches, and `simple_format` splits it into paragraphs on
blank lines. An unescaped `>` or a blank line inside an attribute value could
cause markup in that value, which the sanitizer left inert, to be emitted as
live elements in the helper's `html_safe` output, possibly leading to…
### Impact
`ActiveSupport::MessagePack` serializes an `ActiveSupport::SafeBuffer` as its
bytes alone and deserializes it as a new `SafeBuffer`, so a buffer that is no
longer `html_safe?` (for example, one mutated in place with `gsub!`) is
restored as `html_safe`. If such a buffer holds untrusted input and is stored
through a MessagePack serializer, rendering it after it is loaded bypasses ERB
auto-escaping, possibly leading to XSS.
Applications on Rails 8.1 are affected if they use a MessagePack serializer,
such as `config.action_dispatch.cookies_serializer = :message_pack` or
`:message_pack_allow_marshal`, or `serializer: :message_pack` for
`Rails.cache`, `ActiveSupport::MessageVerifier`…
### Impact
Active Storage's `VideoPreviewer`, `VideoAnalyzer` and `AudioAnalyzer` run
`ffmpeg` and `ffprobe` on a blob's contents without restricting the input
format, so FFmpeg selects a demuxer by probing the file. A blob created
through a direct upload keeps the content type the client declared, so a
client can store an HLS playlist under a `video/*` or `audio/*` type. When
that blob is previewed or analyzed, the `hls` demuxer opens the `file://`
segments that the playlist names, and the preview image or the stored metadata
could disclose a local media file that the application process can read and
whose path the attacker knows.
Applications that accept uploads from untrusted users and…
### Impact
When a GCS service is configured with the `cache_control` option,
`ActiveStorage::Service::GCSService#url_for_direct_upload` signs direct-upload
URLs with V4, and the V4 signature covers only the `Cache-Control` and `Host`
headers. A direct-upload client can therefore send a `Content-Type` of its
choosing, and can omit the declared `Content-MD5`, without invalidating the
URL. GCS stores the supplied content type as object metadata, which takes
precedence over the `response-content-type` that Active Storage sets on
download URLs, so an uploaded object could be served as inline HTML under the
application's bucket path on `storage.googleapis.com`. Script in such an
object runs on…
### Impact
When Active Storage serves a file through its proxy controllers,
`ActiveStorage::Streaming#send_blob_stream` handles an error from the storage
service by calling `head` to send a 404 or 500 response. If the error occurs
after the first chunk has been written, the response is already committed, and
`head` replaces the response body while the server is still reading the
original `ActionController::Live` buffer. That buffer is never closed, so the
server thread handling the request blocks until the process restarts. Repeated
failed downloads could exhaust the server's threads, possibly resulting in a
DoS vulnerability.
Applications are affected when they store files on a service…
### Impact
The Action Mailbox Mailgun ingress authenticates a request with an HMAC of the
`timestamp` and `token` parameters concatenated without a separator, and
`ActionMailbox::Ingresses::Mailgun::InboundEmailsController::Authenticator#recent?`
rejects only timestamps more than 2 minutes in the past. Because the signature
does not fix the boundary between the two values, a captured signed request
can be presented with a timestamp far in the future, which the ingress then
accepts indefinitely. An attacker who obtains one signed Mailgun webhook
request, but not the signing key, could submit arbitrary inbound email that
Action Mailbox processes as genuine, possibly leading to forged…
### Impact
The fix for
[CVE-2026-33168](https://github.com/rails/rails/security/advisories/GHSA-v55j-83pf-r9cq)
skips blank HTML attribute names in Action View tag helpers, but it tests the
name before converting it to a string. A Symbol name made only of whitespace,
such as `:" "`, is not blank, because `Symbol#blank?` is `Symbol#empty?`, so
it reaches the attribute escaping, which emits an attribute with no name and
produces malformed HTML. The asset tag helpers, such as `image_tag`,
`video_tag` and `audio_tag`, symbolize their options, so a whitespace String
name passed to them takes the same path. A carefully crafted attribute value
could then be misinterpreted by the browser as a…
### Impact
When constant lookup raises `LoadError`,
`ActiveSupport::Inflector#safe_constantize` (and `String#safe_constantize`)
builds a regular expression from the requested constant name without escaping
it, so a caller that passes untrusted input lets the attacker choose the
source of a pattern that the application compiles. Crafted patterns can
trigger defects in Ruby's regular expression compiler, possibly leading to a
crash of the server process, and repeated requests could keep the application
unavailable.
Action Cable passes the `channel` value of each `subscribe` command to
`safe_constantize` before any channel lookup, so in the default configuration
an unauthenticated client of…
### Impact
A Rails application that processes attachments with ImageMagick may allow an
unauthenticated attacker to write arbitrary files on the server and, when
attachments are stored with the Disk service, to read arbitrary files readable
by the application process. Those files typically include `secret_key_base`
and credentials for external systems, which may in turn allow escalation to
remote code execution or lateral movement to those systems.
### Details
ImageMagick selects a coder from the content of the file it is given, and some
coders act on files named inside the image rather than on the image itself.
Active Storage constrained none of ImageMagick's coders, and the…
### Impact
Action Pack session stores other than `ActionDispatch::Session::CookieStore`
(`CacheStore`, `MemCacheStore`, and third-party stores built on
`AbstractStore`, `AbstractSecureStore` or the session mixins) write the
session cookie without a `SameSite` attribute, regardless of
`config.action_dispatch.cookies_same_site_protection`, which defaults to
`:lax`. Only `CookieStore` applies the configured policy. In browsers that do
not treat a cookie without the attribute as `SameSite=Lax`, a cross-site
request could carry the session cookie, possibly leading to cross-site request
forgery against actions that do not verify an authenticity token, such as
those of API-only…
### Impact
`ActiveStorage::Service::GCSService#url_for_direct_upload` does not bind the
declared content length into the signed direct-upload URL, and
`#headers_for_direct_upload` returns no length constraint, so the URL
authorizes an upload body of any size. A direct-upload client could store a
file larger than the `byte_size` it declared, and the resulting blob keeps the
declared size, possibly bypassing application file size validations and
quotas. This is the weakness that CVE-2020-8162 fixed in the S3 service.
Applications that store files in Google Cloud Storage and accept direct
uploads are affected.
### Details
The fixed releases sign an `x-goog-content-length-range` header that…
### Impact
`ActiveStorage::Filename#base` and `#extension_with_delimiter`, and therefore
`#extension`, pass the stored filename unmodified to `File.basename` and
`File.extname`, which raise `ArgumentError` when the name contains a NUL byte.
Active Storage stores the client-supplied filename verbatim, so a user who is
permitted to upload an attachment could persist such a filename on databases
that accept NUL bytes in string columns, such as SQLite and MySQL/MariaDB.
Each subsequent request that builds a variant or preview of the blob, opens
it, or renders it as an Action Text attachment then raises, possibly resulting
in a persistent DoS vulnerability for the pages that display the blob…
### Impact
Action Pack negotiates the format of a rendered response from the `Accept`,
`X-Requested-With` and, for XHR requests without `Accept`, `Content-Type`
request headers, but `ActionController::Rendering#_set_vary_header` adds
`Vary: Accept` only when the request carries a usable `Accept` header and the
response has no `Vary` header yet. A response to a request without `Accept`,
or to an XHR request, therefore omits the headers that selected its
representation, and a response whose `Vary` the application set before
rendering (for example `Vary: Accept-Language`) never gains `Accept`. A shared
cache that honours `Vary` could store such a response and serve it to later
clients that…
### Impact
`strip_tags` in Action View returns its sanitized output marked `html_safe`,
but the underlying sanitizer encodes only `&`, `<` and `>`, leaving `"` and
`'` unencoded. Because ERB does not escape a string marked `html_safe`,
untrusted input passed through `strip_tags` and rendered inside a quoted HTML
attribute value could close the attribute and add new attributes, possibly
leading to XSS. Applications that render `strip_tags` output from untrusted
input inside an HTML attribute, for example in a `title` attribute or a
`` tag, are affected. Output rendered as element content is not
affected.
### Details
`strip_tags` now encodes `"` and `'` in its output, as well as `&`, `<`…
### Impact
`ActionController::Live::Buffer#close` pushes its end-of-stream marker onto
the response's bounded queue while it holds the buffer's monitor. When the
client has stopped reading and the queue is full, the push blocks, and
`Buffer#abort`, which runs when the client disconnects or a write times out,
waits indefinitely for the same monitor. Each such response could permanently
hold a web server thread and its `ActionController::Live` thread, possibly
resulting in a DoS vulnerability through thread exhaustion.
Applications that stream responses with `ActionController::Live`, including
`send_stream`, are affected. Applications that serve large files through
Active Storage proxy mode…
### Impact
`ActiveStorage::Blob#open` writes a blob to a tempfile whose name carries the
extension of the uploaded filename, and hands that path to `ffprobe`
(`VideoAnalyzer`, `AudioAnalyzer`), `ffmpeg` (`VideoPreviewer`) and
ImageMagick (`ImageAnalyzer`, the `:mini_magick` variant processor). These
tools select a decoder from the file extension when the content does not
identify its own format, so an attacker who chooses the filename and declares
the content type of an upload chooses which decoder processes it. An FFmpeg
built with AviSynth support treats a file with the `.avs` extension as an
AviSynth script and evaluates it, which could disclose files readable by the
application process…
### Impact
`ActionController::Parameters#each_key` delegates to the internal
`ActiveSupport::HashWithIndifferentAccess` that holds the request parameters,
so when called with a block it returns that hash instead of the `Parameters`
instance. The hash does not respond to `permitted?`, so Active Model mass
assignment (`new`, `assign_attributes`, `update`) accepts it without raising
`ActiveModel::ForbiddenAttributesError`. An application that passes the return
value of `each_key` to mass assignment could allow a remote, unauthenticated
attacker to set attributes that the application does not permit, such as an
authorization flag.
This is an incomplete fix…
### Impact
When `ActionDispatch::Cookies` reads a signed or encrypted cookie that was
written under a rotated secret (`config.action_dispatch.cookies_rotations`) or
in the format that a `:hybrid` cookie serializer migrates from, it writes the
cookie back to the response in the current format, and that write ignores
`request.session_options[:skip]`. `ActiveStorage::DisableSession` relies on
that option to keep the session cookie off the public, immutable responses of
the Active Storage proxy controllers, but those controllers still load the
session, so a request carrying an old-format session cookie could receive the
rewritten session cookie in `Set-Cookie` on a cacheable response. A…
I recently ported my 1996 Pascal/Delphi calculator to Rust. The result is excalc, a cross-platform command-line application and MCP server.
Rust makes it easy to compile an application on every platform. Distributing it via crates.io, MSI, Homebrew or WinGet needed more work. Paste this cookbook into your favorite AI prompt to get it done.
We trigger a distribution from GitHub Releases: a Git tag identifies the new version, CI publishes the crate from that tag, builds portable archives and an MSI from that tag, and attaches everything to the release. Homebrew downloads the tagged source archive. WinGet points at the released MSI.
Prepare the Rust Package
Start with complete package…
What happens when the tool you rely on disappears? For Eduardo and Camila Souza, the shutdown of Pivotal Tracker became the motivation to build Pivotal Tasker, a Rails-based alternative inspired by the project management workflow they missed. They discuss reconstructing the experience from memory and reference material, adding classic and modern interfaces, and integrating coding agents to help create and work through stories.
The conversation expands into their move from consulting toward building products. Eduardo and Camila describe Chamber Builder, an all-in-one platform for chambers of commerce, and a custom sales CRM that brings calling, recordings, transcripts, and…
Five years ago, I announced that RubyTapas had become Graceful.Dev. But a revenant of that feast was left behind: the rubytapas.com site stuck around to handle billing for legacy customers, and I never got around to fully rolling that aspect into Graceful.Dev.
Until now. As of today, RubyTapas.com is no more. If you have a RubyTapas legacy account, it is now managed at Graceful.Dev. Same plan, same price, same access. Nothing changes except the words on the receipts, and there’s nothing you need to do.
And of course, all RubyTapas episodes are here at graceful.dev (as they have been for years). As well as lots and lots of post-RubyTapas content, organized into courses. The old…
Hello, Rails community!
Rails Versions 8.0.5.2 and 8.1.4.1 have been released. These are security updates addressing 21 security issues falling into a few themes:
- Possible arbitrary file read, file write and remote code execution in Active Storage image and video processing
- Possible Strong Parameters bypass in
Parameters#each_key
- Possible IP spoofing in
remote_ip through the Forwarded header (8.1 only)
- Multiple possible XSS vulnerabilities in Action View helpers, and one in the Active Support MessagePack serializer (8.1 only)
- Possible DoS vulnerabilities in Active Support
safe_constantize, Action Controller Live streaming, and Active Storage proxy mode and filename handling
- U…
We strongly recommend upgrading as soon as possible.
Older versions of Rails are unsupported, and users are…
Every business has a list of jobs nobody wants and nobody can automate. Someone logs into a supplier portal every Monday to download invoices, then types them into the accounting software one line at a time. Someone else fills in the same government form every quarter, copying numbers from a spreadsheet into boxes on a website. Neither system offers a way for other software to connect to it (an API), the vendor isn’t going to build an integration for a customer your size, and so the work stays manual.
At the end of October, OpenAI released GPT-6.1 Terra, a model built for what OpenAI calls computer use: it looks at a screen, reads what’s on it, and clicks and types its way through apps and…
We recently moved a client’s Ruby on Rails app off Heroku: a Rails API with Sidekiq workers and several services around it. Before we changed anything, we spent about a week on discovery. We had a kickoff call with the client’s team on a Monday, and then two of us paired for the rest of the week to map the infrastructure and draft a migration plan.
Nothing about that app followed one pattern. Its services were deployed in different ways, and some of the things that mattered most for the migration, like the tools outside Heroku that read from the database and the service that promoted code to production, didn’t show up in the Heroku dashboard at all.
In this article, you will learn what we…
AI can generate a lot of code quickly. There’s a thought that we can catch its mistakes or other issues by reviewing that code. In theory, this may be true; code review can catch bugs. In practice, it only works if the reviewer knows what to look for, has some context and, well, gives a shit.
There's a thought that we can catch mistakes in AI-generated code by reviewing it (or having an AI do that). This is short-sighted thinking, especially as developers lose the skill of code review entirely. Better to use AI to create better tooling.
This is all in a professional context, i.e. at a job. I don't think the conclusions here are necessarily relevant to hobby or open source…
Why Review…
Authors: Vladimir Dementyev, Principal Backend Engineer, Artur Petrov, Backend Engineer, and Travis Turner, Tech Editor
Topics: Rails, AI, Open Source, LLM benchmarking, Ruby, LLMs

A tour of lemans, the open source Ruby harness behind Agents on Rails: eval task anatomy, how it runs and grades coding agents for hours, and how cheaters shaped its guardrails.
Benchmarking coding agents in one sentence: give a model a task, let it work, check the result. The hard part is keeping the race fair. Every model must get the same conditions, and no agent should be able to escape its sandbox or fool its grader (and they try). And all of that has to keep running for hours. In the first post of The Agents…
Milestone: this week I called a freeze on new Hanami work. From here we’re focused on cutting down our outstanding items and then switching to release prep.
I cut down a few items myself: three little pigs things to finish our Hanami CLI changes for compatibility with Dry CLI. This CLI is just right.
After some great feedback from Michael, I made a few adjustments to Dry Logger’s expanded log filtering (like making Regexp filters case insensitive by default, for consistency with string filters), and merged it. Thanks Michael! I also updated Hanami’s default list of filters. Combined with Dry Loggers substring filtering, this will make for a much more filtery out of the box experience.
A…