Over-Defensive AI Code Is the Hangover After Vibe Coding

AI CodingTypeScriptDebuggingDeveloper ToolsDesign Patterns

July 31, 2026

Brutalist hard hat on code printouts with nested shield icons, stencil lettering Over-Defensive AI Code on the hat face

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#

A small demo rocket tips over once while a long fog of shield plates labeled hangover stretches across the page with null check and soft catch labels.
Broken demos fade. Defensive fog is what stays in the repo.

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#

A tiny add(a, b) box sits inside six oversized armor plates labeled null check, empty string, try catch, fallback, optional chain, and default param.
Six defenses around a two-argument add is the texture, not a joke.

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."

before
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";
  }
}
after
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.

View data table
CategoryRelative change (% vs 2023)
Block duplication81
Error-masking constructs47
Within-commit copy/paste41
Two-week code churn15
Error-masking constructs rose 47% while block duplication rose 81% across 623M changes.
Block duplication81% vs 2023
Error-masking constructs47% vs 2023
Within-commit copy/paste41% vs 2023
Two-week code churn15% vs 2023
Source — GitClear Maintainability Gap · 2026-06

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#

Messy hostile inputs hit one thick schema wall, and inside the wall clean code lines end in a fail loud crash when invariants break.
One gate at the edge. Loud failure inside it.

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.

src/http/create-user.ts
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
Share

Newsletter

New posts land in your inbox when they publish. No spam, unsubscribe anytime.

Prefer RSS