Over-Defensive AI Code Is the Hangover After Vibe Coding
July 31, 2026
Over-defensive AI code is the hangover after vibe coding. Not the demo that dies on day two. Not the leaked key that makes the timeline.
The durable cost is quieter. Redundant null checks. try/catch that logs and continues. Defaults that invent domain values. Dual validation stacked on a schema that already did the work.
Models paint defenses on trusted paths because training rewards code that does not throw. Cheap typing flipped the old tradeoff between hardening and shipping.
The hangover is not broken demos#

The culture war still argues about whether vibe coding is real engineering. That fight is mostly process. Skip review, paste a blob, ship. Vibe coding is breaking production when the loop ends at green CI and a shrug.
The hangover shows up after the PR opens. Reviewers still read the diff. Tests still pass. Linters stay quiet. What accumulates is defensive texture that looks like senior care and behaves like fog.
Branching for states that cannot happen. Soft handlers that hide the bug for a quarter. Comments that restate the next line so the model can keep its chain of thought on the page.
Vic Vijayakumar named the economic flip. Hardening used to cost hours, so teams traded risk against paging. When the agent can add another guard in ten seconds, every imaginary edge case becomes "free."
AI review tools then flag those same edges as critical. The latte wins. The codebase inherits a museum of branches nobody can answer "can this be null?" for anymore.
That is not reliability. It is slop wearing a hard hat.
What over-defensive AI code looks like#

The meme version is almost fair. One viral take described AI code prepared for the heat death of the universe on a function that adds two numbers. Null check, empty string, try/catch, fallback, optional chaining, default parameter. All stacked. None earned.
The production version is less funny. A helper already behind Zod or an OpenAPI parse still re-checks every field. A hot pathPATHThe shell environment variable listing directories searched in order when a command name is typed, so every lookup walks each entry until one matches.See also WSLENV, appendWindowsPath, command lookup already validated at structure entry still branches on null in the inner loop. A library serializer already rejects bad payloads, and the model adds a second guard that returns {} so the request "succeeds."
export function greet(user: { id: string; email: string }) {
try {
if (user == null || !user?.email) return "guest";
const email = user.email ?? "unknown@example.com";
return `hi ${email}`;
} catch (e) {
console.warn(e);
return "guest";
}
}export function greet(user: { id: string; email: string }) {
return `hi ${user.email}`;
}The "before" shape matches what AISlop scanners and HN reviewers keep listing.
- Empty or soft catch blocks that log and continue
- Guards stacked on serializers that already reject bad payloads
- Null coalescing that turns missing data into invented data
- Comments that restate Stage 3 of an internal plan
- Dead helpers left after the agent iterated
Sebastian Aaltonen hit the expensive version in graphics work. Codex stuffed defensive checks into hot inner loops where the object had already been validated at data-structure entry. Debt rose a little every day. Cleanup was a separate ask, not a default.
Wade Dorrell's reply to Vic is the trap that locks it in. Tests get written for the defensive branches. Now the slop has a green suite that argues against deleting it.
Models optimize for not crashing#
Karthik S stripped a lot of this deadwood out of a company codebase where roughly ninety percent of the code was LLM-written. The diagnosis is local optimization.
Highlight one function. The model does not fully trust how the dataframe, DTO, or domain object was built upstream, even when that construction is in context. So it wraps the slice until that slice cannot throw.
There is no training penalty for verbosity. There is a huge penalty, in the reward model and in the session, for a traceback. So agents write try/catch so genuine bugs go unnoticed for months.
Daniel Colascione's take is complementary. Verbosity is scratchpad space. The model can clean up later. Later rarely ships. Reviewers inherit the scratchpad as production style.
That is why CLAUDE.mdCLAUDE.mdA markdown file at a project's root that Claude Code CLI reads at the start of every session, holding build commands, conventions, and layout notes so they need not be re-explained.See also /init, context engineering lines like "prefer asserts over soft defaults" help a little and fail often. Session goals still pull toward a finished patch that runs. Soft instructions shape tone. They do not rewrite the objective.
The metric is error-masking, not vibes#
Anecdotes would be enough for a thread. Industry telemetry makes the hangover a maintainability story. GitClear's Maintainability Gap report tracks risk and reuse signals across 623 million analyzed changes from 2023 through 2026.
Error-masking constructs rose 47%. GitClear's related public definition for defensive "obfuscations" includes rescue, safe-navigation operators, null checks, and mock guards.
The paper pairs that rise with block duplication up 81%, within-commit copy/paste up 41%, and two-week churn up 15%, while refactoring and legacy maintenance collapse.
The abstract names the incentive. Default AI workflow delivers a happy path, a passing test, and a closed ticket. The tax is deferred. Reuse, consolidation, and error-surfacing get cheaper to skip and more expensive to restore in year three.
That is the same family of structural rot described in the AI-generated code audit, just one sharper blade. Duplication invents siblings. Over-defense invents silence.
Defense still belongs at the boundary#

Deleting every null check is the wrong slogan. Humans have always stacked unnecessary guards. One HN thread put it cleanly. Unnecessary null checks add branching complexity and make "could this variable ever be null?" unanswerable, which breeds more checks.
Low-level LLM output often fails the opposite way. Secure code-generation research still finds bare-bones C that skips malloc null checks and input validation humans would add. App-layer agents over-armor. Systems agents under-armor. Both are wrong about where the boundary sits.
The rule that survives is simple. Validate once at the trust edge (HTTP body, file parse, third-party webhook, user-controlled string). Inside that edge, types and invariants should hold. When they break, fail loud so the caller must handle it. Do not invent guest, 0, or {} so the happy path stays green.
const Body = z.object({
email: z.string().email(),
name: z.string().min(1),
});
export async function createUser(req: Request) {
const body = Body.parse(await req.json());
return db.users.insert(body);
}
export function displayName(user: { name: string }) {
return user.name.trim();
}Practitioners who fight the agent put this in AGENTS.md style rules.
- Avoid null coalescing for required fields
- Avoid default parameters that hide missing arguments
- Prefer propagation over silent recovery
- Pair soft rules with a linter or post-edit agent that rejects empty catch blocks
Soft prose alone is not enough. The model will soft-handle again on the next edit.
What it costs to hold this line#
Holding the line costs review minutes and ego. The agent looks thorough. The soft catch "prevents outages." Shipping the fog is faster than arguing about provenance.
Teams that use AI tools productively already treat review as the real bottleneck. The bill arrives when nobody can tell which failures are real, which defaults are lies, and which tests only exist to babysit dead branches.
Change mind if the boundary was wrong. Untrusted data inside a "pure" helper is a real bug. Missing authz is a real bug. A NaN from physics code in a hot loop is a real bug.
Those get checks or types at the right place. They do not justify a second null test on a string the parser already produced.
Tomorrow's review habit is short. Ask where the trust edge is. Delete guards inside it. Fail loud. Watch error-masking the way the industry already watches duplication. Over-defensive AI code is not caution. It is the hangover, and it compounds every day the cleanup prompt never runs.
Common questions
No. A check at a real trust boundary (HTTP body, untrusted file, third-party payload) is engineering. A second null check on a value the schema already made non-null is fog. Review the boundary, not the habit.
asked on news.ycombinator.com ↗Training and session goals still reward code that finishes without throwing. Practitioners report agents re-adding soft handlers after explicit fail-loud rules. Soft rules shape tone. Hard bans need hooks, linters, or a review agent that rejects empty catch and silent defaults.
asked on news.ycombinator.com ↗Empty or log-and-continue catch blocks, defaults that invent domain values, dual validation after a schema parse, and guards inside paths that already validated data. Those four patterns hide bugs more than they prevent them.
asked on news.ycombinator.com ↗No. The process problem of shipping unreviewed AI output is covered elsewhere. This hangover is what still lands after review if nobody cuts dead defense. Green tests and clean lints can all pass while error-masking climbs.
asked on noenthuda.substack.com ↗