·Vibe Coding·3 min read·Mid-level developers

The Vibe Coder's Dilemma: When to Read the Code You Just Generated

Reading every AI-generated line is slow. Reading none is reckless. The honest answer is "it depends," and the dependency is more predictable than people admit.

You just got a 200-line PR from your AI assistant. Reading it carefully takes 25 minutes. Skimming it takes three. You have 12 things to ship this week. The temptation is to skim. The right answer is "read the 30 lines that matter, skim the rest." The trick is knowing which 30.

After a year of doing this badly, here's what works for me.

The honest decision tree

You don't actually need to read every AI-generated line. You need to read the parts that match these criteria:

  • High blast radius if wrong: payments, auth, data writes, anything irreversible.
  • External boundaries: webhooks, third-party APIs, queue producers and consumers.
  • Non-obvious logic: conditionals on edge cases, retries, anything concurrent.
  • State transitions: places where invariants must hold across a sequence of operations.

Everything else (wiring, ceremony, framework glue) you can skim. Bug density per line is wildly non-uniform in AI-generated code. Treat it that way. Otherwise you're wasting attention on noise.

A triage script for PR review

HIGH_RISK_PATHS = [
    "billing/", "auth/", "webhooks/", "migrations/",
    "queue/", "permissions/",
]
HIGH_RISK_PATTERNS = [
    r"\.execute\(", r"transaction", r"retry", r"async with",
    r"DELETE FROM", r"UPDATE \w+ SET", r"jwt\.", r"sign\(",
]

def triage(diff):
    must_read, skim = [], []
    for f in diff.files:
        risky = any(p in f.path for p in HIGH_RISK_PATHS)
        risky |= any(re.search(p, f.added_lines, re.I) for p in HIGH_RISK_PATTERNS)
        (must_read if risky else skim).append(f)
    print(f"Read carefully: {len(must_read)} files")
    for f in must_read: print("  ", f.path)
    print(f"Skim: {len(skim)} files")

Why this works

The script doesn't replace reading. It tells you which reading matters. The high-risk paths and patterns capture the parts where AI-generated code most often gets it wrong. Concurrency, retries, SQL, signed tokens. The skim files are where the cost of a missed bug is "the test catches it" or "we revert in five minutes." Time spent on those is time stolen from the parts that actually need eyes.

When to triage instead of full-reading

Any PR over about 100 lines, especially from an AI assistant. Any time velocity is bottlenecking on review, not on writing. Internal tools, prototypes, anything where the cost of a missed bug is bounded. Most weeks for most teams.

When to read every line

Anything touching money, identity, or data you can't recover. Production migrations. Cryptographic code. The first PR from a new AI tool you don't yet trust. Code that will be hard to roll back. In those, the cost of skipping the boring parts is too high to optimise for time.

The triage decision

flowchart TD
    Diff[AI-generated PR] --> Scan["Path + pattern scan"]
    Scan --> H{Touches<br/>high-risk?}
    H -- Yes --> Read[Read carefully<br/>line by line]
    H -- No --> Skim["Skim - check<br/>structure + tests"]
    Read --> Q{Tests cover<br/>edge cases?}
    Q -- No --> Add["Add invariants /<br/>property tests"]
    Q -- Yes --> Approve[Approve]
    Skim --> Approve
    Add --> Approve

Conclusion

Before your next AI-generated PR, write your own version of the triage list. Five high-risk paths in your codebase. Tape it to your monitor. The 80/20 of safe vibe coding isn't "read more." It's "read the right 20% faster." That's it. That's the trick.