I let Claude Code build a Cloudflare app — then broke it the way agents do
If you build with Claude Code, Cursor, or Codex, you know the shape of the problem. 84% of us build with AI now; only 29% trust the output — and the data backs the distrust: 45% of AI-generated apps ship an exploitable vulnerability (Veracode 2025). The agent gets you 70% of the way in minutes. The last 30% — auth boundaries, payments, tenant isolation, data integrity — is where it quietly breaks, and agents get it wrong structurally, not because you're sloppy. It compiles. It looks right. It ships a double-booking on day three.
I wanted to see what happens when that 30% is composed from verified modules behind a contract the agent can't quietly cross. So I scaffolded a real Cloudflare app with create-microservices-app, then made the mistake an agent makes every day — and watched what caught it. Every command and output below is from a real run.
1. Scaffold a working app (one command)
npm create microservices-app@latest booking-demo -- --template booking-sveltekitYou get a full Cloudflare SvelteKit booking app — public booking flow, admin, D1, auth — not a blank starter. The part that matters for agents: it ships its own contract and docs, generated into the repo:
README.agent.md # how to work in this app, safely
docs/llms.txt # machine-readable map for your agent
docs/api-boundary.md # the layering rule: routes are thin adapters
microservices.lock.json # pinned module versions (the contract)
microservices.check.mjs # the executable contract specPoint your agent at README.agent.md first. It opens with "Do not recreate module internals inside this template" — the whole design in one line.
2. What the agent sees before it edits
$ microservices modules list
gateway@0.1.0
auth@0.1.0
identity@0.1.0
customer@0.1.0
booking@0.1.0
audit-log@0.1.0
payment@0.1.0
email@0.1.0Booking logic — availability, slot conflicts, the rules that stop two people booking the same 2pm — lives in the verified @microservices-sh/booking module, not in your route handlers. The layering rule (docs/api-boundary.md): route adapters do HTTP only; the createBooking use case owns the domain; ports + D1 adapters handle data; hooks are where you customize.
3. The baseline check
$ microservices check
Template checks: passcheck isn't a file-exists smoke test. It runs the app's contract spec — asserting the booking route stays a thin adapter over createBooking, the login route adapts @microservices-sh/identity, the slot-constraint migration is intact, and the template never reimplements a module's internals.
4. Now I make the agent's mistake
Here's the request an agent gets all the time: "simplify the bookings endpoint." So I did what an eager agent does — inlined the write straight to the database and dropped the module:
// src/routes/api/bookings/+server.ts — the "simplified" version
export const POST: RequestHandler = async ({ request, locals }) => {
const body = await request.json();
await locals.bookingRepository.insert({
serviceId: body.serviceId,
startsAt: body.startsAt,
customerId: body.customerId
});
return json({ ok: true });
};It type-checks. It runs. In a normal repo it sails through review — and silently drops the slot-conflict guard and customer linkage that createBooking enforced. That's a double-booking waiting to happen: the exact dangerous-30% failure that doesn't surface until production.
Then I ran check:
$ microservices check
Error: One or more generated app checks failed.
$ microservices check --json
FAIL: spec:src/routes/api/bookings/+server.ts
— Booking API route stays a thin adapter over createBooking and injected repositories.It named the exact file and the exact contract I broke — not a vague lint warning, but "you bypassed the verified booking use case." That's a line an agent can read, understand, and fix on its own.
5. Fix and confirm
import { createBooking } from "@microservices-sh/booking";
// ...
const result = await createBooking(await request.json(), {
bookingRepository: locals.bookingRepository,
customerRepository: locals.customerRepository,
actor: locals.user
});
return toSvelteKitResponse(result);$ microservices check
Template checks: passGreen again. The slot-conflict protection is back where it belongs.
Why this matters for agent-driven dev
The point isn't "AI writes bad code." It's that AI writes plausible code, and plausible-but-wrong is the expensive kind. The fix isn't a smarter model — it's a boundary the agent can't cross without a named, machine-readable failure:
- Domain logic (auth, booking, payments) lives in verified modules, not regenerated per project.
- The route layer is a thin adapter — exactly the part you want your agent editing freely.
checkis the contract gate. Wire it into your agent loop and the dangerous 30% stops slipping through.
It's not a black box, either — the generated project is a normal repo you read, edit, and own. You compose the app from verified, source-visible modules — auth, payments, booking — and the contract keeps the composition honest.
And this is the part that matters if you ship for other people: it's the difference between an app that demos and one you can hand to a client, take through a security review, or put in front of an investor — without the 2am breach call. Speed gets you the demo; the contract is what you defend. Do the dangerous 30% once, reuse it safely across every app.
Try it
I verified the scaffold → contract docs → check → break → fix loop above on a real run. The parts that need your machine (a quick install, then dev, then a managed deploy) are yours to run:
npm create microservices-app@latest booking-demo -- --template booking-sveltekit
cd booking-demo
npm install
npm run microservices -- check # the contract gate — run this in your agent loop
npm run dev # http://127.0.0.1:5174If you're building on Cloudflare with an AI agent, point it at README.agent.md, let it move fast on the adapter layer, and make check the thing that has to pass before you ship. That's how you keep the 70% speed without the 30% surprise. Repo and catalog: microservices.sh.