Happy Path Bias¶
Happy path bias is the agent tendency to write code that handles the common case but skips error paths, edge cases, and type boundaries.
Related lesson: Trust Without Verify covers this concept in a hands-on lesson with quizzes.
The pattern¶
AI coding agents skip error handling, edge cases, and type safety. CodeRabbit's analysis of 470 GitHub PRs found that AI-generated code has 2x more error-handling issues and 1.75x more logic and correctness errors than human-written code.
| Symptom | What the agent does | What breaks |
|---|---|---|
| Bare exception handling | except: or catch (e) {} |
Swallows real errors silently |
| Type escape hatches | Reaches for any (TS) or empty-string defaults |
Voids downstream type safety |
| Over-specification | Generates hyper-specific solutions | Fails on variations |
Why it happens¶
The agent aims for task completion: the code compiles, the tests pass, and the requested feature exists. Error handling, validation, and edge-case coverage are implicit requirements that rarely appear in the task description. A catch-all handler or type escape hatch meets that surface goal but defers failures to production.
Detection¶
Linters catch the most common forms before code leaves the editor.
| Rule | Catches |
|---|---|
E722 |
Bare except: |
BLE001 |
Blind exception catching (except Exception) |
TRY003 |
Raising vanilla Exception instead of specific types |
TRY301 |
Abstract raise in except block |
TRY400 |
logging.error() instead of logging.exception() in handlers |
For TypeScript: @typescript-eslint/no-explicit-any and no-unsafe-assignment.
Pre-commit gate¶
# .pre-commit-config.yaml (excerpt)
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.0
hooks:
- id: ruff
args: [--select, "E722,BLE001,TRY003,TRY301,TRY400"]
Mitigation¶
In prompts: "raise ValueError for invalid input, never use bare except" is actionable; "handle errors" is not. Naming anti-patterns by CWE or rule ID reduces vulnerability density by 59 to 64% (Endor Labs).
In CI: lint, type check, then test every agent-generated change. This catches roughly 60% of AI code failures (Augment Code).
In review: look for what the agent omitted — missing finally blocks, absent validation, no error paths in tests.
When this backfires¶
Exhaustive error handling is not always right. It overreaches in these cases:
- Prototyping and throwaway scripts — code that never reaches production can defer error paths, where handling costs more than its signal.
- Framework-managed boundaries — when a runtime or web framework already catches unhandled exceptions at the top level, per-function try/catch adds noise, not recovery. Reserve depth for the runtime exception handling and recovery patterns that the boundary does not cover.
- Tight feedback loops with known input — test harnesses and internal tooling on controlled input rarely need user-facing defensive depth.
- Over-specified exception types — catching
FileNotFoundErrorandPermissionErrorseparately is correct, but catching 15 OS exceptions per function obscures intent. - Linter false positives —
BLE001andTRY003fire on legitimate broad handlers in plugin systems where catchingExceptionis intentional, so blanket rules churn suppressions. - Prompt over-specification — long error-handling instructions in every prompt dilute the task signal with verbose scaffolding.
The anti-pattern targets production-bound code. Enforce at the CI boundary, not the prompt boundary.
Example¶
An agent asked to write a file parser:
def parse_config(path: str) -> dict:
with open(path) as f:
return json.load(f)
def parse_config(path: str) -> dict:
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
raise ConfigError(f"Config file not found: {path}")
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in {path}: {e}")
The first version works when the file exists and contains valid JSON. The second works in production.
Key Takeaways¶
- Agents optimize for the surface goal — compiles, tests pass, feature exists — and skip the implicit error paths, edge cases, and type boundaries.
- Catch the common forms deterministically:
E722,BLE001,TRY003,TRY301,TRY400, and@typescript-eslint/no-explicit-anyat a pre-commit and CI gate. - Name anti-patterns by rule ID in prompts; enforce at the CI boundary, not by padding every prompt with error-handling instructions.
- The bias is a production concern — prototypes, framework-managed boundaries, and controlled-input tooling can reasonably defer exhaustive handling.
Sources¶
- CodeRabbit: AI vs Human Code Generation
- Augment Code: AI Code Failure Patterns
- Ox Security: AI Code Anti-Patterns
- Endor Labs: Anti-Pattern Avoidance Prompts
- Stack Overflow: Bugs and AI Coding Agents
Related¶
- Trust Without Verify — Accepting agent output as correct because it looks polished
- Demo to Production Gap — Code passes demos but fails on real-world edge cases
- Copy-Paste Agent — Type-safety violations from cloning code without adapting types
- Deterministic Guardrails — Hard checks around agent output
- TDD Agent Development — Tests first; agents implement against them
- Pattern Replication Risk — Agents reproduce codebase patterns at scale, including bad error handling
- Exception Handling and Recovery Patterns — Progressive failure hierarchy for agents that encounter errors at runtime
- The Test Homogenization Trap — AI-generated tests share the model's blind spots, missing the same edge cases the code misses