Ronnie Miller

My name is Ronnie. I'm a product-minded software engineer, building SaaS products and the infrastructure that runs them.

I live on California's Redwood Coast and have been building software for over 15 years. That work covers code, cloud infrastructure, security, and leading engineering teams.

Currently, you can find me leading engineering efforts as Partner and CTO of KABI Automotive. I also explore the edges of AI at BuildTestRun and share what I'm building and learning on my YouTube channel, Build at Scale. The notes below are where I write down what I build and what breaks.

My guard hook found nc in cancel

In five weeks my Claude Code guard hook blocked 82 commands in my own projects. 53 were the exfiltration rule, and only one looked like what that rule is for. The agent learned to route around it.

Images in a PR from a fork

I wanted test screenshots in a PR as native GitHub attachments. gh can attach files now, but only with write access to the repo that holds the PR. From a fork into a read-only repo, that left the web editor.

Claude's Bash tool runs my zsh

Claude Code runs Bash tool commands in my login shell, which is zsh. Over five weeks, five zsh behaviors broke bash-style one-liners, and one grep error happened 29 times.

Old code read what the canary wrote

A branch ran against shared databases and queues before it merged. Its jobs, its mailer, and one new STI row reached code on main that had never heard of those classes.

A bare wait also waited for tee

A setup script for a Claude Code cloud environment never finished. A prebuilt Ruby only ran from its build path, and a bare wait was waiting on the tee process that logged the script.

AWS WAF blocked MCP sign-in from desktop apps

An MCP server in a Rails app signed in fine from claude.ai, but desktop clients got a 403 from the load balancer. A managed WAF rule read the loopback redirect URL in the OAuth request as an SSRF attempt.

YJIT under Rosetta was five times slower

A Rails test suite took 5 minutes per suite on an Apple silicon Mac. The container was amd64 under Rosetta with YJIT on, and one spec file ran in 3.5 minutes instead of 28 seconds.

Ruby and Rails 30

  1. Old code read what the canary wrote
  2. YJIT under Rosetta was five times slower
  3. A time on a date column moved the month
  4. CONVERT TO runs after MODIFY
  5. chruby picked the broken Ruby
  6. Three ways Puma didn't restart
  7. Notes from Rails 6.1 to 8.1 in a day
  8. Where the email link 404s came from
  9. A new cookie domain needs a new cookie name
  10. A hash without braces became keyword arguments
  11. driven_by replaced my Chrome driver
  12. The relation ran after the block ended
  13. Two Ruby managers, one gem directory
  14. Setting config.hosts dropped the defaults
  15. Time.local doesn't know the Rails zone
  16. Capybara waited for a request my test never checked
  17. nginx returned 405 for PATCH
  18. Capistrano couldn't authenticate, but ssh could
  19. A Compose volume was hiding my Bundler config
  20. A missing index, found with rack-mini-profiler
  21. wkhtmltopdf in an Alpine image
  22. after_commit and read_changed? never fire together
  23. Connecting Rails to Amazon RDS with SSL
  24. Running Rails migrations on an ECS deploy
  25. Ruby 3.3: the lambda method requires a literal block
  26. Is this memoization thread safe?
  27. Building Ruby 3.0.7 on an M2 Mac
  28. Ruby 2.3 – A Gift for You and Me
  29. RailsConf 2015, Atlanta Georgia
  30. Trying out new features of Ruby 2.2

Command line and Git 27

  1. My guard hook found nc in cancel
  2. Images in a PR from a fork
  3. Claude's Bash tool runs my zsh
  4. A bare wait also waited for tee
  5. My quick tunnel read my named tunnel's config
  6. gh as a one-off git credential helper
  7. chruby picked the broken Ruby
  8. Three ways Puma didn't restart
  9. Wrangler used a token I forgot about
  10. My cd hook was filling Claude's context
  11. localhost was IPv6 on one end
  12. Two Ruby managers, one gem directory
  13. export with no arguments prints everything
  14. find -path "./node_modules/*" only matches the top level
  15. A guard hook that errors lets the command run
  16. Capistrano couldn't authenticate, but ssh could
  17. Moving Neovim to vim.lsp.enable
  18. tmux was running as x86 on my M4
  19. Git doesn't know where a branch came from
  20. Print only the last block from a log
  21. In a rebase, ours and theirs swap places
  22. From a log line to a command on the same box
  23. An alias can't put arguments in the middle
  24. fatal: bad revision 'main' in a GitHub Action
  25. Dear xargs, I appreciate you
  26. Migrate a Git Repository between Local Machines
  27. Vim plugins lost in the Vundle

AWS 22

  1. AWS WAF blocked MCP sign-in from desktop apps
  2. One distribution, two domains
  3. ECS stopped tasks that were still booting
  4. ECS Exec is not a shell
  5. Logs Insights months start in UTC
  6. Counting requests per 15 minutes in Logs Insights
  7. ECS Exec always runs as root
  8. Docker layer caching in CI, and the tag I overwrote
  9. SSM stopped working when I removed the public IP
  10. pulumi import gave me stale code
  11. TLS from the load balancer to the container
  12. Connecting Rails to Amazon RDS with SSL
  13. Running Rails migrations on an ECS deploy
  14. You can't backdate an S3 object
  15. Inspector suppression rules update by ARN, not by name
  16. Packer's env() only works in a variable default
  17. Mount the partition, not the disk
  18. Block Public Access wins over a public bucket policy
  19. From a log line to a command on the same box
  20. Remote Database Connection via ECS Fargate
  21. HTTPS For Your Static S3 Website
  22. Static Website Hosting on Amazon S3

Docker and CI 21

  1. Old code read what the canary wrote
  2. YJIT under Rosetta was five times slower
  3. ECS stopped tasks that were still booting
  4. compose run used the new image, the server didn't
  5. Notes from Rails 6.1 to 8.1 in a day
  6. A frontend deploy broke LLM streaming
  7. ECS Exec is not a shell
  8. driven_by replaced my Chrome driver
  9. WARP blamed 1Password, but Docker was in the way
  10. Setting config.hosts dropped the defaults
  11. Time.local doesn't know the Rails zone
  12. setuptools 82 removed pkg_resources
  13. Making the Claude GitHub Action open the PR
  14. There is no author_association on sender
  15. Deploying a Cloudflare Worker from GitHub Actions
  16. Postgres in Docker ignored my POSTGRES_ settings
  17. ECS Exec always runs as root
  18. A Compose volume was hiding my Bundler config
  19. Cloud Run job args replace the command
  20. Docker layer caching in CI, and the tag I overwrote
  21. wkhtmltopdf in an Alpine image

AI and agents 19

  1. My guard hook found nc in cancel
  2. Images in a PR from a fork
  3. Claude's Bash tool runs my zsh
  4. A bare wait also waited for tee
  5. AWS WAF blocked MCP sign-in from desktop apps
  6. A clean Claude Code inside Claude Code
  7. gh as a one-off git credential helper
  8. The caller talked during a tool call
  9. A frontend deploy broke LLM streaming
  10. My cd hook was filling Claude's context
  11. Claude Code kept dropping on my home Wi-Fi
  12. Claude Desktop started my MCP server with Node 10
  13. export with no arguments prints everything
  14. Hanging up is the hard part
  15. A subagent's model is not the main model
  16. grammY's bot.start() never returns
  17. A guard hook that errors lets the command run
  18. Making the Claude GitHub Action open the PR
  19. There is no author_association on sender

JavaScript 15

  1. Where the email link 404s came from
  2. The caller talked during a tool call
  3. A frontend deploy broke LLM streaming
  4. localhost was IPv6 on one end
  5. One API route on a static Astro site
  6. Claude Desktop started my MCP server with Node 10
  7. Hanging up is the hard part
  8. A subagent's model is not the main model
  9. Tailwind didn't scan my UI package
  10. An old PostCSS config broke the Astro build
  11. grammY's bot.start() never returns
  12. Deploying a Cloudflare Worker from GitHub Actions
  13. Bootstrap 2 caps you at jQuery 2
  14. An axios upgrade dropped my POST body
  15. A date dropdown that broke two ways

Databases 7

  1. Two places MySQL wants another layer
  2. CONVERT TO runs after MODIFY
  3. Postgres in Docker ignored my POSTGRES_ settings
  4. A missing index, found with rack-mini-profiler
  5. Connecting Rails to Amazon RDS with SSL
  6. Remote Database Connection via ECS Fargate
  7. Execute pg_dump against a remote database

Cloudflare 6

  1. My quick tunnel read my named tunnel's config
  2. Wrangler used a token I forgot about
  3. One API route on a static Astro site
  4. WARP blamed 1Password, but Docker was in the way
  5. Claude Code kept dropping on my home Wi-Fi
  6. 633 Worker versions and no way to delete them

This site 5

  1. One distribution, two domains
  2. Make Me A Workflow
  3. Transitioning to a Notebook-style format
  4. HTTPS For Your Static S3 Website
  5. Static Website Hosting on Amazon S3

Python and data 3

  1. setuptools 82 removed pkg_resources
  2. Becoming Friends with Pandas
  3. Jupyter Notebooks: My New Favorite REPL

Integrations 3

  1. 30909 doesn't say what's missing
  2. Configure Bugsnag to sync with Trello
  3. Slack bot for API Documentation

Rust 1

  1. Upgrading a Rust Project

2026

  1. My guard hook found nc in cancel
  2. Images in a PR from a fork
  3. Claude's Bash tool runs my zsh
  4. Old code read what the canary wrote
  5. A bare wait also waited for tee
  6. AWS WAF blocked MCP sign-in from desktop apps
  7. YJIT under Rosetta was five times slower
  8. My quick tunnel read my named tunnel's config
  9. A clean Claude Code inside Claude Code
  10. gh as a one-off git credential helper
  11. One distribution, two domains
  12. A time on a date column moved the month
  13. ECS stopped tasks that were still booting
  14. Two places MySQL wants another layer
  15. CONVERT TO runs after MODIFY
  16. chruby picked the broken Ruby
  17. Three ways Puma didn't restart
  18. compose run used the new image, the server didn't
  19. Notes from Rails 6.1 to 8.1 in a day
  20. Where the email link 404s came from
  21. Wrangler used a token I forgot about
  22. A new cookie domain needs a new cookie name
  23. A hash without braces became keyword arguments
  24. The caller talked during a tool call
  25. A frontend deploy broke LLM streaming
  26. My cd hook was filling Claude's context
  27. ECS Exec is not a shell
  28. localhost was IPv6 on one end
  29. Logs Insights months start in UTC
  30. 30909 doesn't say what's missing
  31. driven_by replaced my Chrome driver
  32. The relation ran after the block ended
  33. One API route on a static Astro site
  34. WARP blamed 1Password, but Docker was in the way
  35. Two Ruby managers, one gem directory
  36. Claude Code kept dropping on my home Wi-Fi
  37. Setting config.hosts dropped the defaults
  38. Time.local doesn't know the Rails zone
  39. Claude Desktop started my MCP server with Node 10
  40. export with no arguments prints everything
  41. Hanging up is the hard part
  42. Capybara waited for a request my test never checked
  43. A subagent's model is not the main model
  44. Tailwind didn't scan my UI package
  45. find -path "./node_modules/*" only matches the top level
  46. setuptools 82 removed pkg_resources
  47. An old PostCSS config broke the Astro build
  48. grammY's bot.start() never returns
  49. A guard hook that errors lets the command run
  50. 633 Worker versions and no way to delete them
  51. Making the Claude GitHub Action open the PR
  52. There is no author_association on sender