Images on my blog were breaking on first load. Every post's cover would render as a grey placeholder until a hard refresh — because Notion's file URLs are signed S3 URLs that expire every hour, and my ISR cache happily served stale HTML for up to an hour past that expiration. First visitors got dead signatures.
That's the bug, and it's not the story.
By the time I sat down to fix it, I had watched several prior AI agents iterate on this blog — polish the home page, tweak the post cards, refactor a component — and none of them had challenged the architecture. The image bug was a symptom, not a root cause. The root cause was that a Notion-powered personal blog did not need a Node runtime on a VPS. It needed static HTML on an edge network, with images mirrored into durable object storage. I knew that before I opened Claude.
So I didn't bring a ticket. I brought a thesis: rearchitect this as static HTML served from Cloudflare Pages, with images copied into R2, and rebuilds driven by Notion webhooks. Then I asked Claude to pressure-test it.
Bringing a thesis, not a ticket
Pair programming with an agent feels different when you arrive with conviction. Claude responded by exploring the current repo in depth, diagnosing the image-expiry race precisely, and asking questions back — not "what do you want?" but "given your thesis, here are the decisions that remain, which should we pick?" I could see the options rendered side-by-side with preview text. I read, I chose. Claude moved on to the next fork.
That pattern — Claude proposes, I decide, Claude builds against the decision — repeated for sixteen hours. It's not a chat. It's a forcing function. Good agents push back; great agents push back with structured options. I don't think I've ever made so many deliberate small decisions so quickly.
The team
Before we started building the serious phases, I asked Claude to design a team. Not to spawn seven parallel agents — to build a framework for pressure-testing decisions across the work ahead. Seven roles:
- Lead (Claude) who wrote every line that shipped.
- Principal Engineer who ran a roundtable with veto power on security and correctness, and pushback on anything that didn't earn its maintenance cost.
- Cloudflare Platform Engineer who knew R2 gotchas and the quirk that Pages Functions bind env vars at deploy time, not at config-save time.
- SRE for structured logging and rollback runbooks.
- DevEx engineer for the CI loop.
- Security Engineer for HMAC verification and the secret scoping matrix.
- Frontend Engineer who cared about whether a callout's light background would still be legible in dark mode.
I set weights — security and correctness first, minimalism second — and when trade-offs surfaced, PE applied them and forced the call. That paid off concretely at the end: a pre-cutover adversarial review from OpenAI Codex surfaced nine items, and PE's weights let us triage to six ship-before-DNS fixes and three deferrals.
Three more agents sat outside the team. Codex for the review. ChatGPT Atlas for driving the Cloudflare admin panel. And me — for secrets, taste, red pen, and the thesis Claude couldn't have generated without my having formed it first.
Delegation by role
Claude wrote code. Every file in the rewrite — the block renderer, the R2 client, the webhook Function, the GitHub Actions workflow, the templates, the CSS — came from Claude. I approved each chunk, tested it in a browser, and merged.
ChatGPT Atlas handled the browser work Claude couldn't touch. Cloudflare's dashboard needs a logged-in session: buckets to create, custom domains to attach, environment variables to paste. Atlas's agent mode drives the cursor directly. I navigated to Cloudflare, logged in, opened agent mode, and handed off the mechanical clicking. Atlas is much better at Cloudflare's admin panel than it would be at AWS's, frankly — the unlock isn't the specific platform, it's that agent mode controls the browser. Once you have a browser agent, any web UI becomes programmable enough to hand off.
Secrets I pasted myself. Values like API tokens and signing keys are the one thing I don't expose to any agent, because they're, by definition, things only I should hold. That narrow boundary is the shape of the work the human actually does in an agentic workflow.
Codex (via its CLI) ran the pre-cutover adversarial review. I gave it a pointed prompt — walk the current branch, find bugs in the classes I care about (race conditions, URL schemes, XSS, signature verification, manifest consistency) and flag only actual production risks. It found nine. Six real issues I fixed in one commit before touching DNS: a JSON-LD breakout XSS via hostile titles, unescaped tag names on post pages, javascript: URL schemes allowed through in links and media, a silent webhook failure mode where GitHub dispatch errors returned 202, a stale image manifest race, and failed-fetch posts leaking into the sitemap. Three I deferred with explicit tradeoffs.
And me. I held the secrets. I made the taste calls — home-page copy, cover crop, callout color, whether to ship KV-backed debounce in v1 or defer (I deferred). I red-penned the work. Saying no is a form of writing. Most importantly, I brought the thesis. Claude couldn't have generated the direction, because the direction was mine already.
The middle: debugging together
The hard part of the session was a two-hour stretch between phases where nothing worked and no single agent was fully at fault. Eight bugs compounded: a recursive block fetch that silently hit an object_not_found on a page Claude's integration couldn't see; p-limit swallowing the error; GH Actions substituting unset secrets as empty strings that zod's .default() didn't recognize as missing; wrangler pages deploy wiping non-secret environment variables from the Pages project on every deploy because they weren't declared in wrangler.toml; a workflow_dispatch call pointed at main when the workflow only existed on the feature branch; Cloudflare Pages binding env vars at deploy time, meaning a PATCH on the project didn't affect the live Function until another deploy.
None of this is unusual. Every one of those bugs is findable by one careful person in one sitting. The interesting thing about debugging with an agent is the rhythm. "Add diagnostic logging, ship it, hit the endpoint, show me the log, make a guess, ship the next try." The feedback loop collapses to tens of seconds. You can be stupid in public with someone who doesn't judge, and iterate yourself into the right answer faster than you would alone.
The breakthrough was watching one real Notion edit trigger the full production chain for the first time. I clicked save in Notion. Within ninety seconds, the production URL reflected the change, and GitHub Actions showed a workflow_dispatch run with Trigger: notion-webhook in the banner. Everything after that was polish.
The meta-loop
This post is a Notion page Claude created via the Notion API. Creating the page fired a page.created webhook to matt.herich.tech/api/webhook. A Cloudflare Pages Function verified the HMAC signature, dispatched a workflow_dispatch event to GitHub Actions, which ran pnpm build, which fetched this exact page back from Notion, rendered it to HTML, uploaded its content-addressed assets to R2 (zero uploads — this post has no images at render time), and deployed the result to the URL in your browser's address bar.
The round trip took about ninety seconds. I watched it happen in one tab, while I sat with Claude in another.
What the human does
In order of time spent, by my rough count: controlling the secrets, making the taste calls, red-penning proposals I didn't want, and — first in importance — bringing the thesis. Everything else is delegatable. And delegation works best when the boundaries are crisp: Claude never tried to log in as me. Atlas never tried to write the renderer. Codex never tried to merge code it wrote. I never tried to write any of it myself.
The full session — twenty commits, about four thousand fewer lines of code than before, a ~723,000-token Claude Code context window that somehow held coherent decisions end to end — is logged in the repo at docs/2026-04-12-rewrite.md if you want the bug-by-bug version. This post is the lesson.

