- AI-generated code doesn't fail loudly. It fails in the three places nothing exercises: auth boundaries, RLS policies, and webhook event coverage
- A template's value isn't the code it saves you writing. It's the code that already survived production
- Prompt for features on top of a debugged foundation, never for the plumbing itself
- Any diff touching
policies/,webhooks/, orauth/gets a human read. Everything else, trust the agent
I've shipped a handful of React Native + Supabase apps this year where an agent wrote most of the code. None of them broke on day one. Two of them broke on day forty, and both times the failure had been sitting in the repo since the first week, green in CI, invisible in the demo.
That's the thing about agent-written full-stack code. It doesn't rot where you're looking. It rots where the happy path never goes.
1. Auth boundary tests that only test the happy path
Ask an agent to "add tests for the profile endpoint" and you get something like this:
it('returns the profile for the current user', async () => {
const res = await api.get('/profile', { headers: authHeader(userA) });
expect(res.status).toBe(200);
expect(res.body.id).toBe(userA.id);
});
Correct, and useless. The test that matters is the one the agent almost never writes unprompted:
it('does not return another user\'s profile', async () => {
const res = await api.get(`/profile/${userB.id}`, { headers: authHeader(userA) });
expect(res.status).toBe(403);
});
it('rejects an expired token', async () => {
const res = await api.get('/profile', { headers: authHeader(userA, { expired: true }) });
expect(res.status).toBe(401);
});
The model optimizes for "the feature works." Nobody in the prompt said "the feature refuses." So the boundary never gets a test, and three refactors later a where user_id = ? clause quietly becomes a where id = ? and nothing catches it.
2. RLS policies that look right and aren't
This is the one that bit me on day forty. The agent added a workout_logs table for a new feature. It wrote the migration, the types, the screen. It also wrote this:
alter table workout_logs enable row level security;
create policy "Users can read their own logs"
on workout_logs for select
using (auth.uid() = user_id);
Looks fine. Reads fine in review. Three things are wrong with it:
- There's no
insertpolicy, so the app "works" only because the agent also wrote the insert through a service-role edge function to get around the error it hit. That function had no auth check of its own. - There's no
updateordeletepolicy, which is safe by accident today and a landmine the day someone adds "edit log." - There's no
with checkclause anywhere, so the moment an insert policy does get added, a user can insert rows with someone else'suser_id.
RLS is where AI-generated code is at its most dangerous, because the failure mode is silent, the code is syntactically perfect, and the agent will route around any error it hits rather than ask why the error exists.
3. Stripe webhooks that handle one event
Every agent-written Stripe integration I've reviewed handles checkout.session.completed. Almost none of them handle:
-
customer.subscription.updated(plan changes, trial ending) -
customer.subscription.deleted(the user cancelled, and your app still thinks they're Pro) -
invoice.payment_failed(card declined on renewal; you now have a paying user with no payment) - Duplicate deliveries. Stripe retries. Without idempotency on
event.id, a retry double-grants credits or double-sends the welcome email
The checkout.session.completed path is the one the agent can verify in a test run. The other four only show up with real customers over real months, which is exactly when you've stopped looking.
Why templates cap the damage
The usual pitch for a starter template is "skip two weeks of setup." That undersells it. Two weeks of typing is not the expensive part. The expensive part is the six months of production that turned the first version of that auth boundary, that RLS policy set, and that webhook handler into the version that actually holds.
A template's value is debugged code, not written code. Specifically:
- The boundary tests already exist, so an agent refactoring auth has something to break.
- The RLS policies come as a complete set (select, insert, update, delete, with
with check), so the agent's job is to add a table that matches the pattern, not invent the pattern. - The webhook handler already switches on the whole subscription lifecycle and dedupes on
event.id, so the agent adds acase, not a file.
The agent is good at extending a pattern that's in front of it. It's bad at inventing the pattern from a one-line prompt. Templates put the pattern in front of it. Something like AppLighter ships exactly these three pieces already wired for Supabase, with the CLAUDE.md and slash commands so the agent reads the conventions before it writes anything. That's the whole reason the sister product, RapidNative, can generate full-stack apps at all: the generated code lands inside a foundation that already knows where the boundaries are.
The 4-step workflow
This is what I do now on every feature that touches auth, data access, or money.
1. Read the template's boundary tests before you prompt. Five minutes. You need to know what "refuses correctly" looks like in this codebase so you can tell when the agent has broken it.
2. Prompt for the feature, not the plumbing. "Add a workout log screen that saves to Supabase" is fine. "Set up RLS for workout logs" is not. The RLS pattern already exists; tell the agent to follow it. If the template has a /add-table command or equivalent, use that instead of describing the migration.
3. Ask for the negative test first. Before the happy path. "Write a test proving user A cannot read user B's workout logs, then make it pass." Agents are perfectly capable of writing boundary tests; they just don't volunteer them.
4. Human-read any diff that touches three directories. policies/, webhooks/, auth/ (or whatever your template calls them). Everything else, review at whatever depth you review agent output today. These three get a line-by-line read, every time, no matter how small the change. That's the entire discipline. It takes about ten minutes per PR.
It's a workflow problem
None of the three rot sites are hard to fix once you know they exist. That's what makes them frustrating: the fix isn't a better model or a better prompt, it's starting from a foundation where the boundaries are already tested and pointing the agent at the boundaries before it writes anything.
The workflow fix is trivially available. Most teams just haven't been burned yet.
If you've found a fourth place agent code rots quietly, drop it in the comments. I've got a suspicion about push token cleanup on logout but haven't caught it in the wild yet.
Top comments (0)