Agents are writing code now. Who's reviewing it?
Y Combinator's latest Light Cone episode is about the AI agent economy. Agents choosing their own tools, making autonomous decisions, writing and deploying code with minimal human involvement. The hosts talk about "make something agents want" as the new motto. Documentation optimized for robots, not humans. Infrastructure built for AI customers, not people. It's a compelling vision. But there's a question worth asking: if agents are writing code autonomously, who's checking whether that code is actually production-ready?
The agent blind spot is the same blind spot, at scale
When a human prompts Cursor or Claude Code, the AI optimizes for "make it work." It writes code that compiles, handles the happy path, and looks clean. The patterns it misses are always the same: auth checks, rate limiting, error handling, secret management. We've documented these patterns extensively. Now scale that up. An agent running autonomously doesn't have a human glancing at the diff before merge. It doesn't pause and think "wait, did I add auth to that server action?" It writes the code, runs the tests (if there are tests), and ships. The same blind spots, but now nobody's in the loop to catch them.
What agent-generated code actually looks like
Here's a pattern we keep seeing. An agent is told to add a new feature, so it creates a server action, adds the database mutation, wires up the UI. Clean, functional, and completely missing an auth check. The agent did exactly what it was asked to do. Nobody asked it to also verify that the caller is authenticated.
'use server'
// Agent was asked: "add ability to delete a team"
export async function deleteTeam(teamId: string) {
await db.delete(teams).where(eq(teams.id, teamId))
await db.delete(teamMembers).where(eq(teamMembers.teamId, teamId))
revalidatePath('/dashboard')
}
export async function transferOwnership(teamId: string, newOwnerId: string) {
await db.update(teams)
.set({ ownerId: newOwnerId })
.where(eq(teams.id, teamId))
}src/app/actions/teams.ts CRIT server-action-auth 'deleteTeam' performs mutation without authentication check CRIT server-action-auth 'transferOwnership' performs mutation without authentication check'use server'
export async function deleteTeam(teamId: string) {
const session = await auth()
if (!session?.user) throw new Error('Not authenticated')
// Verify the caller actually owns this team
const team = await db.query.teams.findFirst({
where: and(eq(teams.id, teamId), eq(teams.ownerId, session.user.id))
})
if (!team) throw new Error('Not found or not authorized')
await db.delete(teamMembers).where(eq(teamMembers.teamId, teamId))
await db.delete(teams).where(eq(teams.id, teamId))
revalidatePath('/dashboard')
}Agents don't add guard rails unless you make them
The second pattern is error handling. An agent builds an API route that proxies to an external service. It handles the success case perfectly. The failure case? An empty catch block, or no error handling at all. When the external service goes down, your users see a 500 with a stack trace. Or worse, the error gets swallowed and the operation silently fails.
// Agent built an AI chat endpoint
export async function POST(req: Request) {
const { messages } = await req.json()
const completion = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
})
return Response.json(completion)
}src/app/api/chat/route.ts CRIT error-handling API route handler 'POST' has no error handling INFO rate-limiting API route has no rate limitingexport async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1'
const { success } = await ratelimit.limit(ip)
if (!success) {
return Response.json({ error: 'Too many requests' }, { status: 429 })
}
try {
const { messages } = await req.json()
const completion = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
})
return Response.json(completion)
} catch (err) {
console.error('Chat API failed:', err)
return Response.json({ error: 'Something went wrong' }, { status: 500 })
}
}"Make something agents want" means machine-readable output
The YC discussion about agent-friendly tools cuts both ways. If agents are choosing tools based on how easy they are to parse, then code quality tools need to speak the agent's language too. A linter that only outputs human-readable text is useless to an agent pipeline. It needs structured output: JSON with file paths, line numbers, rule IDs, severity levels, and fix hints. Something an agent can read, interpret, and act on without a human in the middle.
$ prodlint
Found 3 issues in your codebase.
Check your server actions and API routes.$ prodlint --json
{
"findings": [
{
"ruleId": "server-action-auth",
"file": "src/app/actions/teams.ts",
"line": 4,
"severity": "critical",
"message": "'deleteTeam' performs mutation without auth check",
"fix": "Add authentication check before database mutation"
},
{
"ruleId": "rate-limiting",
"file": "src/app/api/chat/route.ts",
"line": 2,
"severity": "info",
"message": "API route has no rate limiting",
"fix": "Add rate limiting middleware"
}
],
"overallScore": 42,
"filesScanned": 12,
"summary": { "critical": 1, "warning": 0, "info": 1 }
}The CI gate for agent-generated code
In an agent-first workflow, code quality checks can't be optional and they can't require human judgment. They have to be automated, deterministic, and fast. An agent writes code, pushes a commit, and the CI pipeline runs prodlint. If it finds critical issues, the build fails. The agent reads the structured output, fixes the issues, and tries again. No human needed for the common cases. Humans review what the automated checks can't catch. This isn't theoretical. It's how linters have always worked in CI. The difference is that the developer reading the lint output is now also an AI. The loop is agent writes code, linter checks code, agent fixes code. The linter is the only deterministic part of that loop. It doesn't hallucinate, it doesn't drift, it doesn't depend on the prompt. It checks the same 52 rules every time and gives the same answer.
Check your agent's output
If you're building with AI agents or letting them ship code autonomously, add prodlint to your pipeline. One command, zero config, structured JSON output that agents can parse and act on. Deterministic results, 52 rules covering security, reliability, and AI-specific patterns. The agent economy is real. The code quality gap is real too. Close it before your agent ships something you wouldn't have approved.
npx prodlint --json src/app/actions/teams.ts CRIT server-action-auth Server action performs mutation without auth src/app/api/chat/route.ts CRIT error-handling API route has no error handling INFO rate-limiting API route has no rate limiting supabase/migrations/003.sql CRIT supabase-missing-rls Table 'agent_logs' has no row level security 4 files scanned — 3 critical, 1 infoCatch all of these automatically.
52 production readiness checks. Zero config. Under 100ms.