---
title: Ask what the green check proved
description: When a check goes green, ask what it proved, then what it didn't. Addy Osmani's checklist for critical thinking when agents do the work, held up against my own setup, where agents merge and deploy about 80 pull requests a day.
date: 2026-11-04
tags: AI coding agents, code review, verification, critical thinking
---

When a check goes green, ask what it proved. Then ask what it didn't. That's the advice I keep from Addy Osmani's new article on critical thinking when agents do the work.

It matters to me because my coding agents now merge and deploy their own pull requests. The article came out five days after I gave them that last step. In that first week, 557 pull requests merged on my GitHub account. So I held his checklist up against my setup. It held up where I had defined "verified" before the work, and failed where I trusted a green check for more than it checked.

Addy works on Claude Code at Anthropic, and he likes agents. He says so first: this isn't a call to use fewer of them. [Read the article on X](https://x.com/addyosmani/status/2117512471930167377), posted on November 3.

It's my third note on code review. The first was [review the plan, not the pull request](/notes/review-the-plan-not-the-pull-request.html). The second was [how the Claude Code team reviews code that Claude writes](/notes/claude-code-team-code-review.html), a post Addy co-wrote.

## The slow slide into "looks fine"

Here's the trap. Agent answers look right most of the time now. They're well written, mostly correct, and they don't ask for a second look. You can run ten agents at once, but you still have only one pair of eyes.

Addy draws three levels of trust in an agent:

1. Offloading. You hand over the task, but you still own the result.
2. Deferral. The answer becomes your next input. You no longer treat it as a guess to test.
3. Surrender. The answer quietly becomes yours, and you stop checking it. "Cognitive surrender" is a term from Steven Shaw and Gideon Nave, two researchers at Wharton.

Nobody gets there in one step. You read one answer carefully. It was good, so you skim the next. Of the third, you only glance at the outline. Then someone points at one line and asks why it's there. You can't say, not even with your agent's help.

That's his test: if you can't explain the answer, even with your agent, it isn't yours anymore. Sometimes that's fine. Addy let an AI pick between three similar hotels for a course recording, and he'd do it again. He wouldn't do it for money, for his children's careers, or to decide if code is safe to ship.

<figure class="fd-fig gc-spec" aria-labelledby="gc1-cap" data-desc="Addy Osmani's three levels of trust in an agent, going down. One, offloading: you hand over the task, but you still own the result. Two, deferral: the answer becomes your next input, no longer a guess to test. Three, surrender: the answer quietly becomes yours, and you stop checking it. Under them, how people slide down, in four steps: read it, because the last answer was good; skim the next one; glance only at the outline of the one after; then, asked about a line, you can't explain it. The test: if you can't explain the answer, even with your agent's help, you've given it up.">
<ol class="mp-trio gc-stair">
<li><p class="mp-trio-n"><b>1</b>Offloading</p><p class="mp-trio-what">You hand over the task, but you still own the result</p></li>
<li><p class="mp-trio-n"><b>2</b>Deferral</p><p class="mp-trio-what">The answer becomes your next input, no longer a guess to test</p></li>
<li class="gc-low"><p class="mp-trio-n"><b>3</b>Surrender</p><p class="mp-trio-what">The answer quietly becomes yours, and you stop checking it</p></li>
</ol>
<p class="rp-k gc-k">How you slide down</p>
<ol class="rp-steps gc-drift">
<li><p class="rp-step-n"><b>01</b>Read it</p><p class="rp-step-s">The last answer was good</p></li>
<li><p class="rp-step-n"><b>02</b>Skim it</p><p class="rp-step-s">So you skim the next one</p></li>
<li><p class="rp-step-n"><b>03</b>Glance at it</p><p class="rp-step-s">Only the outline of the one after</p></li>
<li class="gc-lost"><p class="rp-step-n"><b>04</b>Lose it</p><p class="rp-step-s">Asked about a line, you can't explain it</p></li>
</ol>
<p class="mp-strip"><b>The test</b><span>If you can't explain the answer, even with your agent's help, you've given it up.</span></p>
<figcaption id="gc1-cap">Addy's three levels, then the slide down them, one reasonable step at a time. "Cognitive surrender" is Shaw and Nave's term.</figcaption>
</figure>

## Eighty pull requests a day

Until October, every change ended with me. My [How I work with AI](/ai.html) page still says so: "a pull request I read and merge". That sentence is now out of date.

It changed in two steps. On October 8, eight pull requests had been waiting for days in the repo that holds my agents' setup. Two were stacked, one built on top of the other. So I told each agent: merge and clean up the pull requests you open. On October 29, I removed the last exceptions, my finance app, my health apps and this site among them. And I added one more step: deploy.

The rule lives in `CLAUDE.md`, the file my coding agents read when a session starts. Once a change is verified and GitHub says it can merge, the agent merges it and deploys it. Then it checks the live app and cleans up after itself. It stays that way until I say otherwise.

The same paragraph pins down "verified": tests, CI (the checks GitHub runs on every change) and a real browser for anything with a screen. That's Addy's advice: decide what "verified" means before you start, because afterwards you'll find a reason to accept any result. My definition was there before the first agent merged anything. So no agent decides afterwards what counts as done.

And the volume is exactly his point. From October 29 to November 4, 557 pull requests merged across 65 repos on my GitHub account. About 80 a day. Nobody reads 80 pull requests a day, and the rule doesn't ask me to.

<figure class="fd-fig gc-rule" aria-labelledby="gc2-cap" data-desc="How my changes ship, before and after the merge rule. Before, as my How I work with AI page describes it in September: I ask for a change, an agent opens the issue and builds it on its own branch, the agent proves it works, and I read the pull request and merge it. Since October 29: I ask for a change, an agent opens the issue and builds it on its own branch, the agent proves it with tests, CI and a real browser, and the agent merges it, deploys it and checks the live app. Verified means tests, CI, a real-browser check for any screen, and GitHub saying the pull request can be merged. From October 29 to November 4, 557 pull requests merged across 65 repos on my GitHub account, about 80 a day.">
<div class="mp-two-row">
<div class="mp-two-card">
<p class="mp-two-who">September, on my AI page</p>
<p class="mp-two-name">I read and merge</p>
<ol class="rp-flow">
<li><span class="rp-who">Me</span>Asks for a change</li>
<li><span class="rp-who rp-who-ai">Agent</span>Opens the issue, builds on its own branch</li>
<li><span class="rp-who rp-who-ai">Agent</span>Proves it works</li>
<li class="rp-rev"><span class="rp-who">Me</span>Reads the pull request and merges it</li>
</ol>
</div>
<p class="mp-two-vs">then</p>
<div class="mp-two-card mp-two-peter">
<p class="mp-two-who">Since October 29</p>
<p class="mp-two-name">The agent merges</p>
<ol class="rp-flow">
<li><span class="rp-who">Me</span>Asks for a change</li>
<li><span class="rp-who rp-who-ai">Agent</span>Opens the issue, builds on its own branch</li>
<li><span class="rp-who rp-who-ai">Agent</span>Proves it: tests, CI, a real browser</li>
<li><span class="rp-who rp-who-ai">Agent</span>Merges, deploys, checks the live app</li>
</ol>
</div>
</div>
<p class="rp-metrics"><span class="rp-k">Verified means</span><span>tests</span><span>CI</span><span>a real browser for any screen</span><span>GitHub says it can merge</span></p>
<div class="mp-stats">
<p><b>557</b><span>pull requests merged, October 29 to November 4</span></p>
<p><b>65</b><span>repos on my GitHub account</span></p>
<p><b>80</b><span>pull requests a day, on average</span></p>
</div>
<figcaption id="gc2-cap">The last step moved from me to the agent. The counts come from a GitHub search of merged pull requests on my account.</figcaption>
</figure>

## A green check proves one thing

Addy's warning: you can give up on evidence without noticing. "Is there evidence that supports my conclusion?" quietly turns into "did the check turn green?" Agents make tests cheap, so there's green everywhere. But a test written with the same mistake as the code still passes. And a check covers one thing, even when it feels like it covers everything.

His finish line is proof you can point at: a passing test, a screenshot, a log, a trace. When an agent says "done", he first reads the command it ran and what came back. The agent's own summary comes last.

Three times in my own setup, something passed and proved less than it seemed:

- The stylesheet. In September, a note on this site went live with its diagrams unstyled. The agent had checked the CSS with a cache-buster, an extra bit in the URL that skips the cache. So the agent got the new file, but readers' browsers still had the old one. The check proved the server had the new CSS. It didn't prove anyone would see it.
- The blank page. A script wrote a page into my notes app, a OneNote replacement I host myself. Then it read the page back through the API, and the two matched. But in the app, the page was blank: one empty text node in a table cell made the editor refuse the whole page. The check proved the server stored the page, not that the page opens.
- The daily backup. The same app saves a copy of its database every day and keeps the last 14. The job runs without an error. But the clean-up also counts 14 older one-off backups whose file names start the same way. So it deletes each new copy right after writing it. Every step does its job, and there is no daily backup. The bug is still open as I write this.

<figure class="fd-fig gc-green" aria-labelledby="gc3-cap" data-desc="Three things from my own setup that passed, with what each proved and what it missed. The stylesheet: the check passed and proved the server had the new CSS, fetched past the cache; it missed that readers' browsers kept the old file, so a note's diagrams went live unstyled. The blank page: the check passed and proved the API stored the page a script wrote into my notes app; it missed that one empty text node in a table cell made the editor refuse the page, so it opened blank. The daily backup: the job ran without an error and proved a copy is written every day; it missed that the clean-up counts older one-off backups whose file names start the same way, and deletes each new copy right after it is written. That bug is still open. Addy's line: when a check goes green, ask what it proved, then ask what it didn't.">
<div class="cr-vs">
<div class="cr-vs-row"><p class="cr-vs-k">The stylesheet<span class="gc-pass">passed</span></p><p><span class="rp-k">Proved</span>The server had the new CSS, fetched past the cache</p><p><span class="rp-k gc-miss">Missed</span>Readers' browsers kept the old file. The diagrams went live unstyled.</p></div>
<div class="cr-vs-row"><p class="cr-vs-k">The blank page<span class="gc-pass">passed</span></p><p><span class="rp-k">Proved</span>The API stored the page a script wrote</p><p><span class="rp-k gc-miss">Missed</span>One empty text node, and the editor refused the page. It opened blank.</p></div>
<div class="cr-vs-row"><p class="cr-vs-k">The daily backup<span class="gc-pass">no error</span></p><p><span class="rp-k">Proved</span>A copy is written every day</p><p><span class="rp-k gc-miss">Missed</span>The clean-up deletes it right after. Still open.</p></div>
</div>
<p class="mp-strip"><b>Addy</b><span>When a check goes green, ask what it proved. Then ask what it didn't.</span></p>
<figcaption id="gc3-cap">Two checks that passed and one job that ran without an error, from this site and my notes app. Each was true about one thing only.</figcaption>
</figure>

## Every mistake becomes a rule

This is where my setup does what Addy asks. When something isn't good enough, he says, write down why "in shared config, a hook or a test so that it doesn't happen again. That's how taste compounds."

A hook is a tiny script that runs before an agent's command and can say no. Most of my agent rules were born from a mistake:

- Docker, stopped twice. On macOS, `pkill` reads every option written after the first pattern as one more pattern. An agent wrote its options last. They matched Docker Desktop and stopped every container, on October 13 and again on October 22. Since October 25, a hook refuses that word order and prints the fixed command. I replayed 830 old commands through it: 12 refused, and all 12 were real mistakes.
- Port clashes. Agents kept taking ports that another app already used. Since October 15, a hook checks docker and Tailscale commands against my list of ports before they run. It has 43 tests.
- Unstyled diagrams. That one became a rule: open the page in a real browser, at phone and desktop widths, on my machine and again on the live site. This site has a checker that does exactly that, in Chromium and WebKit, the engines behind Chrome and Safari.
- Tests on live data. On October 25, an agent ran my films app's tests inside the live container. The container's database path won over the one the tests set, so the tests deleted the live tables. The film list was rebuilt from the copy on the drive that holds the films. The resume points came back from that morning's backup, minus everything watched after it. This rule is a line in my agents' memory and a fix in that app's tests. It isn't a hook yet, so it works only as long as an agent reads it.

<figure class="fd-fig gc-hooks" aria-labelledby="gc4-cap" data-desc="Four mistakes from my setup, the rule each one became, and where the rule lives now. One: pkill read options placed after the pattern as more patterns and stopped every Docker container, twice in October; the rule is options before the pattern, and it lives in a hook that refused 12 of 830 older commands, all real mistakes. Two: agents took ports another app already used; the rule is that the port list decides, and it lives in a hook with 43 tests. Three: a note's diagrams went live unstyled; the rule is a real browser at phone and desktop widths, locally and live, and it lives in a check script that runs Chromium and WebKit. Four: tests run inside a live container wiped my films app's tables; the rule is that tests never run in a live container, and it lives only in my agents' memory and one app's tests, not in a hook yet. Addy's line: write down why in shared config, a hook or a test, so it doesn't happen again.">
<ol class="gc-fix">
<li><p><span class="rp-k">The mistake</span>pkill read the options after the pattern as patterns and stopped every container, twice</p><p><span class="rp-k">The rule</span>Options before the pattern</p><p class="gc-where gc-w-hook">Hook<em>12 of 830 old commands refused, all real</em></p></li>
<li><p><span class="rp-k">The mistake</span>Agents took ports another app already used</p><p><span class="rp-k">The rule</span>The port list decides</p><p class="gc-where gc-w-hook">Hook<em>43 tests</em></p></li>
<li><p><span class="rp-k">The mistake</span>A note's diagrams went live unstyled</p><p><span class="rp-k">The rule</span>A real browser, phone and desktop, local and live</p><p class="gc-where gc-w-script">Check script<em>Chromium and WebKit</em></p></li>
<li><p><span class="rp-k">The mistake</span>Tests in a live container wiped my films app's tables</p><p><span class="rp-k">The rule</span>Tests never run in a live container</p><p class="gc-where gc-w-memo">Memory<em>not a hook yet</em></p></li>
</ol>
<p class="mp-strip"><b>Addy</b><span>Write down why in shared config, a hook or a test, so it doesn't happen again.</span></p>
<figcaption id="gc4-cap">Each rule is a mistake I already paid for. The dashed one still depends on an agent reading it.</figcaption>
</figure>

## I scored my setup on his questions

Addy keeps a short list of questions he asks himself. I tried five of them on my setup, and most found a gap.

"Did I form a hypothesis before asking?" Every change starts as a GitHub issue, and a hook refuses a pull request without one. But the issue says what I want, not what I expect.

"What would have to be true for this to be wrong? Then go find out." My real-browser check asks exactly that, with a script, on every visual change. This one holds.

"Have I heard the counter argument from a different angle?" Only partly. In the pipeline that runs while I'm away, a model from another company reviews the change before the pull request opens. Everywhere else, the agent that built the change also grades it.

"What did it decide for me?" Addy asks the agent to list its choices: the defaults, what it left out, which requirement won when two clashed. My pull requests say what changed and how it was tested. Nothing in them asks for the choices.

"What am I no longer practicing?" He writes that list every month. Nothing in my setup tracks it.

So one holds, one holds partly, and three are gaps. My setup checks the result well, but barely checks my own thinking, before the work or after.

<figure class="fd-fig gc-score-fig" aria-labelledby="gc5-cap" data-desc="Five of Addy Osmani's self-check questions scored against my setup. Did I form a hypothesis before asking? The issue says what I want, not what I expect: a gap. What would have to be true for this to be wrong? A real browser at 320 pixels and on desktop, local and live: holds. Have I heard it from another angle? A model from another company reviews, but only in the pipeline that runs while I'm away: partly. What did it decide for me? Pull requests say what changed, not what the agent chose: a gap. What am I no longer practicing? Nothing in my setup tracks it: a gap. In total, one holds, one holds partly and three are gaps.">
<p class="gc-score-h" aria-hidden="true"><span>His question</span><span>My setup</span></p>
<ol class="gc-score">
<li><p class="gc-q">Did I form a hypothesis before asking?</p><p class="gc-mine">The issue says what I want, not what I expect</p><p class="gc-v gc-v-gap">Gap</p></li>
<li><p class="gc-q">What would have to be true for this to be wrong?</p><p class="gc-mine">A real browser at 320 px and on desktop, local and live</p><p class="gc-v gc-v-hold">Holds</p></li>
<li><p class="gc-q">Have I heard it from another angle?</p><p class="gc-mine">Another company's model, only in the pipeline that runs while I'm away</p><p class="gc-v gc-v-part">Partly</p></li>
<li><p class="gc-q">What did it decide for me?</p><p class="gc-mine">Pull requests say what changed, not what it chose</p><p class="gc-v gc-v-gap">Gap</p></li>
<li><p class="gc-q">What am I no longer practicing?</p><p class="gc-mine">Nothing in my setup tracks it</p><p class="gc-v gc-v-gap">Gap</p></li>
</ol>
<div class="mp-stats">
<p><b>1</b><span>holds</span></p>
<p><b>1</b><span>partly</span></p>
<p><b>3</b><span>gaps</span></p>
</div>
<figcaption id="gc5-cap">His questions, shortened, against what my setup does today. The checks on the result hold. The ones on my thinking mostly don't.</figcaption>
</figure>

## Where I'm still just hoping

Addy has a name for the gap between what your agents produce and what you'd sign: the orchestration tax. When finished work piles up faster than you can check it, one more agent session won't help. Either make the checks stronger, so less of the work needs you, or slow down. Then his sharpest line: "When you're producing more than you can stand behind, you're no longer reviewing. You're hoping."

On October 8, I didn't slow down. I stepped out of the merge and kept the checks. Two of the hooks above arrived in the next three weeks. That's his first option, partly done. Three places in my setup still run on hope:

- Notes that publish themselves. Every Monday, a script drafts up to three short notes for this site from what I shipped. Since September 26, it also publishes them, with no human step. The safety net I designed for it is [still an open issue](https://github.com/brbousnguar/brbousnguar.github.io/issues/135): a second model to check the facts, a privacy and style check, and a one-tap unpublish.
- A phone fix. On October 31, a fix for my notes app merged and deployed. Its own pull request said: "Not provable here: the AutoFill bar and the real keyboard geometry." The agent said so plainly. The fix still shipped, before anyone tried it on an iPhone.
- The backup bug from above. An agent found it and filed it. Nobody has fixed it yet.

Addy also asks what happens when the models are better at this than we are. His answer: someone still answers for what ships. He quotes Anthropic's research on its own engineers, which calls it the paradox of supervision: "supervising Claude requires the very coding skills that may atrophy from AI overuse." Then: "You may offload the work and much of the auditing, but not the accountability." My rule moved the merge button. It didn't move the accountability.

<figure class="fd-fig gc-hope" aria-labelledby="gc6-cap" data-desc="Three places in my setup that still run on hope, with their status. One: short notes on this site are drafted every Monday from what I shipped and publish themselves since September 26; the safety net designed for them, a second model to check the facts, a privacy and style check and a one-tap unpublish, is not built. Two: a phone fix in my notes app merged and deployed on October 31 with the line 'Not provable here: the AutoFill bar and the real keyboard geometry'; it shipped before anyone tried it on an iPhone. Three: the daily backup bug in my notes app, found and filed by an agent, is still open. Addy's line: when you're producing more than you can stand behind, you're no longer reviewing, you're hoping.">
<ol class="gc-score gc-score-open">
<li><p class="gc-q">Notes that publish themselves</p><p class="gc-mine">Every Monday since September 26. Fact check, style check and unpublish are designed, not built.</p><p class="gc-v gc-v-part">Safety net not built</p></li>
<li><p class="gc-q">A phone fix in my notes app</p><p class="gc-mine">"Not provable here: the AutoFill bar and the real keyboard geometry."</p><p class="gc-v gc-v-part">Not tried on a phone</p></li>
<li><p class="gc-q">The daily backup bug</p><p class="gc-mine">Found and filed by an agent, October 31</p><p class="gc-v gc-v-gap">Bug open</p></li>
</ol>
<p class="mp-strip"><b>Addy</b><span>When you're producing more than you can stand behind, you're no longer reviewing. You're hoping.</span></p>
<figcaption id="gc6-cap">What my setup ships with nobody standing behind it yet, as of November 4.</figcaption>
</figure>

## Try it

Addy ends with an exercise that costs two lines. Before you ask, write down what you think will happen. When the agent is done, write down one thing it didn't check.

His other habits, in short:

- Have a guess, even a bad one.
- Read what the agent ran and what it returned before you read its summary.
- Ask it what it decided for you.
- Write the request in your own words: who it's for, what's out of scope, what done means.
- Once a week, do one real task without an agent.
- If you wouldn't put your name on it, it doesn't go out.

In my setup, the two cheapest gaps to close are his first and fourth questions: one line in each issue for what I expect, and a list of the agent's choices in each pull request.

<figure class="fd-fig gc-try" aria-labelledby="gc7-cap" data-desc="Addy Osmani's closing exercise as two cards. Before you ask: write what you expect, even a bad guess, because once you've read a well-written answer it's hard to imagine another. After it's done: write one thing it didn't check, because a green check means one thing. Under them, his habits: once a week, one real task without an agent; once a month, a list of what you've stopped doing. And his question: how lost would you be if AI didn't exist?">
<div class="mp-two-row">
<div class="mp-two-card">
<p class="mp-two-who">Before you ask</p>
<p class="mp-two-name">Write what you expect</p>
<p class="mp-two-line">Even a bad guess. Once you've read a well-written answer, it's hard to imagine another.</p>
</div>
<p class="mp-two-vs">then</p>
<div class="mp-two-card mp-two-peter">
<p class="mp-two-who">After it's done</p>
<p class="mp-two-name">Write what it didn't check</p>
<p class="mp-two-line">One thing. A green check means one thing, so name another.</p>
</div>
</div>
<p class="rp-metrics"><span class="rp-k">Once a week</span><span>one real task without an agent</span></p>
<p class="rp-metrics"><span class="rp-k">Once a month</span><span>a list of what you've stopped doing</span></p>
<p class="mp-strip"><b>His question</b><span>How lost would you be if AI didn't exist?</span></p>
<figcaption id="gc7-cap">The two lines from the end of the article, then the habits he suggests around them.</figcaption>
</figure>

If you want more:

- [Critical thinking when agents do the work](https://x.com/addyosmani/status/2117512471930167377), Addy's article, about 15 minutes
- [Cognitive surrender](https://addyosmani.com/blog/cognitive-surrender/), his earlier post on the term
- [The orchestration tax](https://addyosmani.com/blog/orchestration-tax/), on the gap between what agents produce and what you'd sign
- [How AI is transforming work at Anthropic](https://www.anthropic.com/research/how-ai-is-transforming-work-at-anthropic), the research behind the paradox of supervision
- [How the Claude Code team reviews code that Claude writes](/notes/claude-code-team-code-review.html), my notes on the post Addy co-wrote

Try the two lines on your next agent task. Then tell me what your agent didn't check.
