The site that deployed itself
This site is 82 commits old. Eighty-one of them were written with Claude, including the first one, which created the Cloudflare config. What changed was not my typing speed.
0 views
This site went from an empty directory to a production deployment on 31 August. It is now six days old and carries 82 commits. Eighty-one of them end with the same trailer:
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The exception is a merge commit.
That includes the first one. feat: scaffold blog on Astro + Cloudflare Workers Static Assets created the Astro config, the content collection schema, the
routes, and wrangler.jsonc — the file that tells Cloudflare what to serve and
on which domains. There was never a hand-built version that Claude later
improved. The deployment configuration and the first line of application code
arrived in the same commit.
I want to be precise about the part that is genuinely interesting, because it is not “AI writes code fast.” Everyone has seen that demo. The interesting part is which half of my job survived.
What the pipeline actually is
There is no CI service in this repo. No GitHub Actions workflow, no build server, no deployment dashboard I click. The entire path from idea to production is four commands, and all four run from the same terminal where the code gets written:
npm test # 104 tests
npm run build # 54 pages
npx wrangler deploy
git push origin main
That is the whole pipeline. It works because the platform absorbed the parts
that used to need a service. Astro compiles to static HTML. Cloudflare Workers
Static Assets serves that HTML from the edge. Wrangler reads wrangler.jsonc
and provisions what the config declares. The configuration is the
infrastructure, and configuration is a file, and files are the thing an agent is
already good at.
This matters more than it sounds. When your infrastructure is a dashboard, an agent cannot help you — it can only tell you which buttons to press, and you are still the one pressing them. When your infrastructure is a text file in the repository, the agent operates it directly, and the whole distinction between “writing the app” and “running the app” quietly collapses.
The feature I did not build today
Today I added view counts and a like button. Ten typed prompts, seventy-eight minutes, first prompt to pushed commit. The first prompt was:
can we add visit counter and like feature for our website? look at free tier CF for storage
Two spaces after “counter”, because I typed it quickly. The rest of that sentence — “look at free tier CF for storage” — is the only technical direction I gave, and it is not a design. It is a constraint.
What came back was the design. Cloudflare’s free tier allows 1,000 KV writes per day. A view counter writes once per view, so KV would have been exhausted before lunch on a good day. D1 allows 100,000 row writes per day, roughly a hundred times what this site would ever need. I did not know either of those numbers. I did not look them up. The choice between the two stores — the only decision in the whole feature that could not be undone cheaply — was made by reading the platform’s own limits and comparing them against the write pattern.
The same held for a subtler one. This site had deliberately shipped with no
Worker script: a comment in wrangler.jsonc said adding one “would invoke a
Worker per request for zero benefit.” Counters need a server. That comment was
an obstacle, and the obvious move is to delete it and move on.
Instead the property it was protecting got preserved. run_worker_first is
scoped to /api/*, so page requests are still served straight from static
assets and never invoke the Worker at all. Then — and this is the part I care
about — it was proved, not assumed. The Worker’s non-API path was temporarily
instrumented with a log line, six static requests and one API request were sent
to the deployed site, and the production logs showed exactly one invocation. The
static requests never touched it.
Nobody asked for that check. I would not have thought to ask for it.
Where the agent was wrong, and how I know
I am not making a claim about infallibility. Three things went wrong today that are worth naming, because they are the reason the workflow is trustworthy rather than the reason it is not.
The first batch endpoint returned every row in the stats table. Testing showed it returning thirty-four rows — including every throwaway slug the test suite had ever created — meaning in production the response would have grown forever and shipped junk to every visitor. It was caught by looking at the actual output, not by reasoning about the code.
A verification step used grep -c to count occurrences across five pages and
reported “1” for every one of them. That number was meaningless: grep -c
counts matching lines, and minified HTML is one line. The correct counts were
eight, twelve, thirteen, three and twelve.
An invocation check used timeout, which does not exist on macOS. The command
never ran, and the empty output was briefly read as “no invocations” — a clean
result that was actually no result at all.
Every one of those was found and stated. That is the workflow: the agent writes the code, and then the agent tries to break its own claim about the code. The guard tests in this repo were each verified by deliberately introducing the regression they were supposed to catch and confirming they failed. A test you have never seen fail is decoration.
What my job is now
I typed ten prompts today. None of them contained a schema, a SQL statement, a
cache header, or an API shape. They contained things like move the counter in the header after the tag and did you do the counters on only wiring page, not others?.
Read that list again and notice what it is. It is not instruction. It is
specification and audit — deciding what should exist, and then checking whether
the claim that it exists is true. The prompt all code checked in and pushed to origin? is the entire job description. The answer that came back was
“committed, not pushed” — seven commits sitting locally while the site was
already live in production, because wrangler deploy ships from the working
tree and does not care what your remote thinks.
That gap between deployed and pushed is exactly the kind of thing that used to be caught by a CI pipeline, and now gets caught by asking.
There is one boundary that stayed where it was. Creating the D1 database, setting the production secret, and running the first deploy of a Worker-fronted site were all held until I said so explicitly. Those are the operations that create real resources on a real account and change what strangers see. Everything reversible ran on its own; everything outward-facing waited. That division is not a limitation of the tooling. It is the only part of the setup I would insist on keeping.
The part that transfers
If you want an agent to run your deployment pipeline, the useful move is not a better prompt. It is choosing infrastructure that lives in files.
I did not pick Cloudflare Workers for this site because of latency numbers. I picked a stack where the routing table, the domain bindings, the database binding, the cron schedule, and the secret names are all declared in one committed file that a coding agent can read, edit, and verify. Every capability this site gained in six days — static hosting, then a database, then a scheduled job — was a change to that file plus a command.
Pick the stack whose control plane is a text file, and the deployment problem turns into a code problem, which is a problem you already have help with.