justin․searls․co

The reason AI startups like Windsurf, Cursor, et. al, are all fucked is that "you’re a product, not a platform" is the new "you’re a feature, not a product."

If you fail to secure a massive acquisition, you're gonna get steamrolled by the big tech companies as soon as they catch up.

First impressions of GitHub Copilot's Agent mode

tl;dr it works. You should try it.

I finally got around to trying the new Agent functionality in GitHub Copilot.

You gotta really know when to ask an LLM for help, though. (I wrote last year how I decide whether to reach for an AI assistant, if you're interested.)

My experience and recent research both indicate that AI works best for creating new stuff from scratch, especially when that code is perfunctory and typical and conventional. That's the reason there's such a divide between people who've had terrific experiences with "vibe coding" and others—like me—for whom all the hours fucking with AI to make it do my job for me have mostly been a waste of my time. Every other time I've tried to use an agent mode in Cursor or Claude Code, I've asked it how to do things that most "staff" or "principal" or "distinguished" engineer have never tried and would have no idea how to do, so naturally they never went well.

But today I'm finally starting work on a new gem, and I had an annoying task that wasn't a legacy rescue, or exploiting a brand new language feature, or doing something totally unlike anything anyone has ever done before. So I fired up the agent mode using Anthropic's 3.7-sonnet model and let it go to work.

Specifically, I asked it to stub out a Rails engine to use as a starting point for moving over existing login functionality into a gem. It did basically everything in the same order and way that I would. It commented out additional hooks I'll probably need, which is nice.

It also gave me instructions for wiring up the new engine from the project I'm extracting the behavior from, which is a nice bonus.

Did it work? Yup:

Cool.

Breaking Change artwork

v34 - Bait and Switch 2

Breaking Change

Nothing like a peaceful Sunday morning at the end of an exhausting, historically-volatile week to pour a hot cup of coffee and spew absolutely scalding takes in all directions. If you get burned, don't say I didn't warn you. Read the message on the lid.

We've done 34 of these now and my mailbag is getting full of old e-mails that don't make sense anymore. Please email new stuff to me at podcast@searls.co and we, like civilization, will start fresh next time!

Video of this edition of the show is up on YouTube.

You can read more about things on other websites below:

Show those show notes…

Microsoft CTO Kevin Scott apparently just said:

"95% of code is going to be AI-generated (in the next five years)," Scott said. But before developers start panicking, he reassured that “it doesn’t mean that the AI is doing the software engineering job…. authorship is still going to be human.”

Panic? Never been a better time to start a company focused on cleaning up bad code and aiding broken organizations and then billing by the hour.

Anyone who still believes the quantity of code one owns is an asset and not a liability is a fool.

In general, I love how Japanese society prioritizes cleanliness, but one thing that's never made sense to me is how the rice sold at supermarkets is absolutely FULL of germs.

How can I ensure e-mail delivery?

Prompt:

A customer of betterwithbecky.com has reported that they aren't receiving authentication e-mails, and therefore cannot access their account. The customer's e-mail address uses a custom domain but is hosted by Google. The app sends e-mail from app-mail.betterwithbecky.com via Amazon SES, which reports everything is configured correctly.

Attached are the complete DNS Zone records for betterwithbecky.com as well as the headers of a raw e-mail message I just received after attempting to log in via Gmail.

Here's what the computer said:
(Generated by OpenAI o3-mini-high)

Analysis of Email Deliverability Issues for betterwithbecky.com

Background and Symptoms

The domain betterwithbecky.com uses Amazon SES to send transactional emails (login verification messages) from support@betterwithbecky.com. Amazon SES is configured with a custom MAIL FROM subdomain (app-mail.betterwithbecky.com), and SPF, DKIM, and DMARC DNS records have been published. Amazon SES reports these emails as “delivered” (the recipient’s mail server accepted the message with a 250 OK), yet multiple users – especially those with Gmail addresses – do not receive the emails in their inbox or spam folder. This suggests that Gmail is likely accepting the messages but then filtering or discarding them internally. We need to examine possible causes, including authentication alignment, Gmail’s filtering criteria, and any configuration or content issues that could lead to silent dropping of emails.

Email Authentication Configuration (SPF, DKIM, DMARC)

Proper email authentication is the foundation of deliverability. In this case, betterwithbecky.com has implemented all three mechanisms:

Turns out, there's more to it…

The best argument against e-mail based authentication is that it’s impossible to guarantee the delivery of e-mails. Once a user says they've checked spam, there's basically nothing else you can do for them beyond checking your configuration for the millionth time.

I don't mean to pick on Pawel Brodzinski in this blog post, but I stopped reading right at the top:

In its original meaning, Kanban represented a visual signal. The thing that communicated, well, something. It might have been a need, option, availability, capacity, request, etc.

I hate to come off as a pedant here, but something that's always annoyed me about the entire family of Lean practices in the Western world is the community's penchant for its uncritical adoption of regular-ass nouns and verbs from Japanese. Lean consultants have spent literal decades assigning highly-specific nuanced meanings to random words, and if you actually listen to anyone introducing Lean, it's hard to go 5 minutes without getting the icky sense the use of those words is being deployed to trade on appeals to nonsensical Oriental exoticism. I've lost track of how many times I've heard something like, "according to the ancient Japanese art of Kaizen," or similar bullshit.

It's true that Lean's existence is owed to the work of luminaries like Deming, Ohno, and Toyoda and their development of the Toyota Production System, but what eventually grew into the sprawling umbrella term "Lean" was based on surprisingly brief and incomplete glimpses of those innovations. As a result, the connective tissue between Lean as it's marketed in the West and anything that ever actually happened in Japan is even more tenuous than most Lean fans probably realize. So the fact that everyone carries on using mundane Japanese words as industry jargon makes even less sense.

For example, here are some words Lean people use and what they actually mean:

And so on.

As an entitled white man, I'll be the first to admit I don't lose much sleep over cultural appropriation. I'm just saying, if you're trying to come up with a name for a specific concept or process, remember that existing words have meaning before cherry-picking a noun from a foreign language textbook and calling it a day.

UPDATE: Just as I was worried I might have been a bit too harsh here, I realized his blog has comments.

This one is just incredible:

A post it note is not a kanban

Theo, you might have to reconsider your idea of “idiocy”, potentially in front of a mirror. “Kanban” is not a noun so of course a post-it can’t be one. The concept originated from Japan (Toyota factories to be specific) so it makes absolute sense to use the original word. Their method did not use a signboard at all, Kanban is the system, which you would learn with a couple minutes of focused googling.

Of course, open a dictionary and you'll see that kanban (看板) is categorized under meishi (名詞), which (unless the Lean folk have some other made up definition for it), means noun.

Well, Theo, we use a Japanese name because that’s where it came from. Have you ever heard of a tsunami, or kamikaze, or sushi? These are also Japanese words we use in the English which have more nuanced meanings than just googling their “literal translation”.

Additionally, I can understand that being as unintelligent as you are must be difficult but if you try your hardest you might be able to google “kanban” and “signboard” to learn that one refers to a methodology and the other does not.

For example, real expert Lean practitioners know that "ahou" (阿呆) refers to observing a mistake repeatedly and forming an expensive twelve step correction plan, even though its literal translation is "idiot."

Ruby makes advanced CLI options easy

If you're not a "UNIX person", the thought of writing a command line application can be scary and off-putting. People find the command line so intimidating that Ruby—which was initially populated by a swarm of Windows-to-Mac migrants—now boasts a crowded field of gems that purport to make CLI development easier, often at the cost of fine-grained control over basic process management, limited option parsing, and increased dependency risk (many Rails upgrades have been stalled by a project's inclusion of another CLI gem that was built with an old version of thor).

Good news, you probably don't need any of those gems!

See, every UNIX-flavored language ships with its own built-in tools for building command line interfaces (after all, ruby itself is a CLI!), but I just wanted to take a minute to draw your attention to how capable Ruby's built-in OptionParser is in its own right. So long as you're writing a straightforward, conventional command line tool, it's hard to imagine needing much else.

This morning, I was working on me and Aaron's tldr test runner, and I was working on making the timeout configurable. What I wanted was a single flag that could do three things: (1) enable the timeout, (2) disable the timeout, and (3) set the timeout to some number of seconds. At first, I started implementing this as three separate options, but then I remembered that OptionParser is surprisingly adept at reading the arcane string format you might have seen in a CLI's man page or help output and "do the right thing" for you.

Here's a script to demo what I'm talking about:

#!/usr/bin/env ruby

require "optparse"

config = {}
OptionParser.new do |opts|
  opts.banner = "Usage: timer_outer [options]"

  opts.on "-t", "--[no-]timeout [TIMEOUT]", Numeric, "Timeout (in seconds) before timer aborts the run (Default: 1.8)" do |timeout|
    config[:timeout] = if timeout.is_a?(Numeric)
      # --timeout 42.3 / -t 42.3
      timeout
    elsif timeout.nil?
      # --timeout / -t
      1.8
    elsif timeout == false
      # --no-timeout / --no-t
      -1
    end
  end
end.parse!(ARGV)

puts "Timeout: #{config[:timeout].inspect}"

And here's what you get when you run the script from the command line. What you'll find is that this single option packs in SEVEN permutations of flags users can specify (including not setting providing the option at all):

$ ruby timer_outer.rb --timeout 5.5
Timeout: 5.5
$ ruby timer_outer.rb --timeout
Timeout: 1.8
$ ruby timer_outer.rb --no-timeout
Timeout: -1
$ ruby timer_outer.rb -t 2
Timeout: 2
$ ruby timer_outer.rb -t
Timeout: 1.8
$ ruby timer_outer.rb --no-t
Timeout: -1
$ ruby timer_outer.rb
Timeout: nil

Moreover, using OptionParser will define a --help (and -h) option for you:

$ ruby timer_outer.rb --help
Usage: timer_outer [options]
  -t, --[no-]timeout [TIMEOUT] Timeout (in seconds) before timer aborts the run (Default: 1.8)

What's going on here

The script above is hopefully readable, but this line is so dense it may be hard for you to (ugh) parse:

opts.on "-t", "--[no-]timeout [TIMEOUT]", Numeric, "Timeout …" do |timeout|

Let's break down each argument passed to OptionParser#on above:

  • "-t" is the short option name, which includes a single a single hyphen and a single character
  • "--[no-]timeout [TIMEOUT]" does four things at once:
    • Specifies --timeout as the long option name, indicated by two hyphens and at least two characters
    • Adds an optional negation with --[no-]timeout, which, when passed by the user, will pass a value of false to the block
    • The dummy word TIMEOUT signals that users can pass a value after the flag (conventionally upper-cased to visually distinguish them from option names)
    • Indicates the TIMEOUT value is optional by wrapping it in brackets as [TIMEOUT]
  • Numeric is an ancestor of Float and Integer and indicates that OptionParser should cast the user's input from a string to a number for you
  • "Timeout…" is the description that will be printed by --help
  • do |timeout| is a block that will be invoked every time this option is detected by the parser. The block's timeout argument will either be set to a numeric option value, nil when no option value is provided, or false when the flag is negated

If a user specifies multiple timeout flags above, OptionParser will parse it and invoke your block each time. That means in a conventional implementation like the one above, "last in wins":

$ ruby timer_outer.rb --timeout 3 --no-timeout
Timeout: -1
$ ruby timer_outer.rb --no-timeout --timeout 3
Timeout: 3

And this is just scratching the surface! There are an incredible number of CLI features available in modern Ruby, but the above has covered the vast majority of my use case for CLIs like standard and tldr.

For more on using OptionParser, check out the official docs and tutorial.