%20(1).jpg)
How can teams ship AI-written code safely?
AI-written code can reach production safely when the workflow requires design verification, adversarial plan review, automated checks after each change, browser testing, and a final human decision. Across 17 pull requests, this pipeline caught 32 issues, including an IDOR, before release.
The AI needed thirty-two corrections and introduced an IDOR. None of it reached production.
One of my review subagents caught an IDOR in code the model had just written: any user could write to any chat by changing a UUID in the request. It never reached production. What caught it was another instance of the same model that introduced the bug.
Over a single session the agent also pushed to the remote twice without asking, shipped react-icons where the design specified a custom SVG, and painted a button #FFFFFF where Figma said #0F1A20. Seventeen PRs, two repositories, fifty-odd files, thirty-two post-implementation corrections in all. None of them became a production bug.
Nine months ago I published 212 Sessions, my last piece on supervising this workflow. In it I measured an 89% success rate, 4x faster median delivery, and two production incidents against eleven on standard prompting. Since then the tooling changed and the method sharpened. What used to be "write specs, then ultrathink" now looks more like a DevOps pipeline than a conversation, and the reason is the thing 212 got half-right.
The agent starts by reading the task itself. I paste a monday.com task URL and it pulls the title, assignee, status, the full description with its acceptance-criteria checklist, linked Figma URLs, and attached screenshots over MCP. Nothing gets copy-pasted or summarized; it sees exactly what the PM wrote.
Then it reads the design, because the most expensive iterations come from an agent that builds from memory. The first time I let it work that way, it produced react-icons instead of the custom Figma SVG, the wrong border color, button text at #FFFFFF instead of #0F1A20, a 1px separator where the design called for 0.5px, and a 24px border-radius instead of 8px. Ten piecemeal fixes later, this went into project memory:
Before implementing ANY UI component, call get_design_context and get_screenshot for the specific Figma node. Check every detail: colors, borders, icons, padding, hover states, tooltips. Do NOT implement from memory.
Now, for every component, the agent pulls the exact CSS from the Figma node, captures the visual reference, and checks every property against the plan before writing anything. That one rule cut about 80% of UI fix iterations.
The agent plans in read-only mode, unable to edit anything but the plan document. Two or three explorer subagents map the terrain first: current component structure, the theme system and design tokens, similar features already built. The plan they feed covers context, a file-by-file change summary, execution order with a dependency graph, and how to verify the result end to end.
Then two subagents tear that plan apart before a single line of code exists, one on architecture and security, one on TypeScript patterns and conventions. On the chat redesign the first pass flagged eight problems, most of them structural: a content-width change that hit every user, UnauthSidebar dropped into the wrong architectural layer, a ConfirmModal duplicated instead of reused, and SVG icons that needed forwardRef to work with Chakra. A second pass, fresh from zero, caught four more, down to semantic-token keys that had to be nested objects instead of flat strings.
Twelve issues found and killed before implementation started. A spec tells the agent what to build; it does not tell you the font change is global and will break your auth pages. After agents approve the plan, it is time for me to step in, and in most cases, something will still be missing.
Once the plan survives review, building is mechanical. All styling goes into the Chakra v3 theme system: a custom _unauth condition via a data-unauth attribute, semantic tokens that auto-resolve on auth state, button recipe variants (brandSolid, brandOutline), and text styles for every pattern in the design. Zero conditional styling in components. One data-unauth="true" on the container and everything downstream resolves.
A subagent pulls the SVGs from Figma, converts them to React components with forwardRef and SVGProps, and barrel-exports ten typed icons in one parallel task.
Each component gets written and immediately verified: npx tsc --noEmit, then yarn lint, then yarn build, then a Playwright screenshot against Figma. TypeScript runs after every single file edit, not once at the end. On the backend, the sequence is the same: code, lint, format, tests, and API-level E2E, all without me. After several rounds of agent review, I step in.
The failures that matter surface in code review, where the same two subagents read the full diff in parallel: one hunting architecture drift and security holes, the other enforcing zero any, real discriminated unions, and pattern compliance. On the chat redesign this took four rounds. The first found seven problems, including nested interactive HTML and a batch of semantic tokens created but never used. The next two rounds cleared five more between them, down to a bottom-margin change that leaked onto every mobile user instead of just the unauthenticated ones. The fourth was clean.
Nested interactive HTML and a leaked margin are not the bugs that make headlines. The IDOR from the opening is.
The tech lead noticed that the copy button was hidden for unauthenticated users: readOnly gated both copy and the like/dislike reactions. "Correct behavior," the reviewer wrote. "Unauth is read-only, no interactions." The fullstack enforcer agreed the readOnly guard was clean code: one boolean, one gate, no special cases.
They were both wrong. Copy is a read action. The user copies text to their own clipboard, nothing touches the server. Like/dislike is a write action: it persists feedback to the database and requires a user ID. Gating both behind readOnly conflated a security boundary, write operations, with a UX convenience, clipboard. I told the agent to split the gate:
const showCopy = message.message != null && message.message.length > 0;
const showReactions = isSaved && !readOnly;
Two lines. Neither reviewer suggested this: they were debating whether the existing gate was correct, not whether the gate itself was the wrong abstraction. That is the tie-break: seeing that both sides are answering the wrong question.
The same model implemented that split in two lines once I saw it, and had defended the broken gate twice when it reviewed itself. Genius and idiot in the same weights, and I have never found the line between them.
After review, the agent drives the running app in a headless browser and checks the states a diff cannot prove:
Each check saves a screenshot to disk for comparison against Figma.
Plan-review findings are not included. This count begins after implementation and covers code, UI, process, CI, and merge failures.
The merged PRs hide this history. Without structured review, some of these corrections would have shipped as bugs, some as broken process, and the rest would have waited for the next developer to trip over them.
Each correction turns into a rule, and the rules persist across sessions as markdown files:
feedback_figma_first.md : Verify each component against Figma BEFORE implementing
feedback_no_commit_without_review.md : STRICTLY FORBIDDEN to commit without explicit user approval
feedback_simplifier_danger.md : Simplifier can revert uncommitted work, commit/stash first
feedback_dco_signoff.md : Always use git commit -s for DCO Signed-off-by
feedback_full_review_report.md : Report ALL review findings verbatim, never filter
The agent reads these at session start. They are non-negotiable. The one that changed the most:
Never commit, push, or create PRs without the user's EXPLICIT instruction. Even if the plan says "commit and push" as a step, STILL ask.
That came from the agent committing and pushing without my review, twice. If an error reflects a general failure mode, the rule goes into global memory; otherwise, it stays with the project.
The ML integration ran the whole pipeline on a single task: replace the mock chat handler with real LLM streaming from an external service. Plan created, two review rounds, eighteen issues found and fixed, then approved. The implementation included:
withRetry for connection resilience, with the HTTP status check inside the callbackisMlConfigured guard, with no default QA URLis_processing flag managementStreamEventEmitterThe reviewers caught six things that were production bugs waiting to happen:
data: lines were filtered out, breaking paragraph breaks in markdowndone event was not sent on error paths, leaving the frontend hangingThat IDOR got the same treatment as the whitespace bug and the hung frontend beside it: reported verbatim, fixed, verified.
The agent is Claude Code, running as a CLI process that sits inside the repo all day, rather than a chat window I paste into. It reads tasks from monday.com and designs from Figma Dev Mode over MCP, drives a headless Playwright browser for the E2E checks, and controls my actual Chrome for debugging.
The review layer is subagents with fixed jobs: a tech-lead-architect for architecture, security, and breaking changes; a fullstack-typescript-enforcer for types, patterns, and coverage; a code-simplifier for dead code and naming; and a verify-app agent that runs the browser checks.
Every commit clears a Lefthook chain before it lands: TypeScript, license check, ESLint, Prettier, JSDoc, and unit tests. Every PR clears two of those reviewers. Then it clears me. I read every diff by hand. I never opened an IDE to write code. I opened it to read files and point at Figma elements. Across seventeen PRs, the test suite stayed green.
Nine months ago I wrote: "Most engineering failures aren't about complexity, they're about vague specifications we code around instead of fixing."
After thousands more sessions, that is half the story. A perfect spec still ships with wrong icon imports, CSS shorthand that overrides explicit values, type assertions where discriminated unions belong, missing finally blocks on async operations, and mobile edge cases that only surface at 375px. The spec caught most of it. The review pipeline caught most of what the spec missed. The browser caught what neither a plan nor a diff could prove. Across these seventeen PRs, the gates produced thirty-two corrections. None reached production.
Every gate that matters ends in a human decision. The agent writes, reviews, and tests, but a person decides which finding is real, which of its decisions to overrule, and whether the feature is done. Without someone at the gates, the pipeline emits findings and calls it governance. The difference between producing those findings and preventing the problems from shipping was me.
None of the implementation work was unusually difficult. These were junior-level tasks, and I chose them on purpose, so the piece would not reduce to "the model is stupid." Even at that level, it needs supervision. The problems that leave scars, the ones with no clean spec and a dozen systems touching each other, are where it degrades fastest.
This run is not why I believe this. It is the one I counted. Across thousands of sessions, the details changed and the pattern did not.
The quality collapse I wrote about last year reads to me less like AI writing worse code and more like teams shipping its first draft with the review layer torn out. I cannot prove that is the cause. I can tell you what this pipeline refused to skip.
The model earns its place. I am faster than I was, and throughput went up, though the work is less fun than it used to be. AI can take a feature end to end. Without the gates, quality degrades dramatically, and across the industry it has already fallen off a cliff. Automation alone cannot pull it back. The human is the switch between poor quality and good.
The AI wrote the code. The gates shipped it.