Skip to content

Best Practices · Software Engineering

7 AI Coding Mistakes That Are Quietly Destroying Codebases — And How We Fix Them

AI coding tools accelerate development. They also introduce subtle, systemic problems that most teams do not notice until it is too late. Here are the 7 AI-specific mistakes we have seen across 11 client projects, and the patterns we use to prevent them.

Anurag Verma

Anurag Verma

13 min read

7 AI Coding Mistakes That Are Quietly Destroying Codebases — And How We Fix Them

Sponsored

Share

Every article about AI coding tools focuses on the upside: faster development, increased productivity, more tests, better documentation. And the upside is real. We have measured it.

But there is a downside that the industry is not talking about honestly. Over the past 18 months, we have worked on 11 client projects that use AI coding tools extensively. In every single one, we found patterns of damage that were unique to AI-generated code: problems that would not exist if the code had been written manually.

These are not bugs in the traditional sense. The code works. The tests pass. The features ship. But the codebase is silently rotting in ways that will cost real money to fix later. Here are the 7 mistakes we see repeatedly, with specific examples and the patterns we use to prevent them.

Code Quality The most dangerous AI coding mistakes are the ones where the code works perfectly, until it does not.

Mistake 1: Ghost Technical Debt

What it is: Code that works correctly, passes all tests, and nobody on the team can explain how it works.

This is the most insidious problem with AI-generated code. When a developer writes code manually, they build a mental model of how it works as they write it. When an AI generates code and the developer reviews it quickly and merges it, the mental model does not exist. The code is a black box that happens to produce the right output.

A real example from a client project: We inherited a Next.js application where the data fetching layer was entirely AI-generated. It worked flawlessly, until a third-party API changed its response format. The error manifested as incorrect data on a dashboard, but the root cause was buried in a chain of transformations that nobody understood. The developer who had prompted the AI to write it had left the company. The current team spent 3 days tracing through code that would have taken 30 minutes to debug if someone had written it manually and understood the logic.

The cost: 3 days of senior developer time. The fix itself was 4 lines of code.

How we prevent it:

Our "Ghost Debt" Prevention Rules:
├── 1. Every AI-generated function gets a JSDoc comment written
│      by the HUMAN, not the AI. If you cannot write the comment,
│      you do not understand the code.

├── 2. Complex logic gets a "why" comment, not just a "what"
│      comment. AI writes "what" comments perfectly. Only humans
│      can explain "why": the business context, the trade-offs,
│      the alternatives that were considered.

├── 3. No "magic" utility functions. If a utility does something
│      non-obvious (complex regex, bit manipulation, custom
│      encoding), the developer must be able to explain it in
│      a code review. If they cannot, it gets rewritten.

└── 4. Monthly "explain the code" sessions. Random sections of
       the codebase are selected and developers explain them to
       each other. If nobody can explain a section, it gets
       flagged for documentation or rewriting.

Mistake 2: The Copy-Paste Architecture

What it is: AI generates new implementations of functionality that already exists in the codebase, leading to duplicated logic scattered across multiple files.

This happens because AI tools have limited context. When you ask Claude Code or Cursor to “add a function to validate email addresses,” it generates a new email validation function, even if there is already one in src/utils/validation.ts. The new one might be slightly different (different regex, different error messages), creating inconsistency.

Over time, this compounds. We audited one client project and found:

  • 4 different email validation functions (3 AI-generated, 1 original)
  • 3 different date formatting utilities
  • 2 different error handling patterns
  • 5 different ways to make HTTP requests to the same internal API

The cost: Developer confusion, inconsistent behavior for users, and every bug fix needs to be applied in multiple places.

How we prevent it:

# In our CLAUDE.md / project context files:

## Existing Utilities (ALWAYS use these, never create duplicates)

### Validation
- Email: `src/utils/validation.ts``validateEmail()`
- Phone: `src/utils/validation.ts``validatePhone()`
- URL: `src/utils/validation.ts``validateUrl()`

### Formatting
- Dates: `src/utils/format.ts``formatDate()`, `formatRelative()`
- Currency: `src/utils/format.ts``formatCurrency()`
- File size: `src/utils/format.ts``formatFileSize()`

### HTTP
- Internal API: Always use `src/lib/api-client.ts`
- External APIs: Always use `src/lib/http.ts` with retry logic

### Error Handling
- API errors: Use `AppError` class from `src/lib/errors.ts`
- User-facing errors: Use `ERROR_MESSAGES` from `src/constants/`

DO NOT create new utility functions without checking this list first.

This project context approach reduces duplication by about 80%. The remaining 20% gets caught in code review.

Mistake 3: The Security Blind Spot

What it is: AI-generated code systematically deprioritizes security in favor of “making it work.”

This is not a subtle problem. It is a systematic one. AI models optimize for correctness (does the function produce the right output?) and completeness (does it handle the specified requirements?). Security is a secondary concern, if it is considered at all.

Real examples from our audits:

// AI-generated: Input validation that looks correct but is incomplete
const createUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.string(), // ← Should be z.enum(['user', 'admin', 'editor'])
                     // AI accepted any string, including 'admin'
                     // This was a privilege escalation vulnerability
});

// AI-generated: File upload without proper validation
app.post('/upload', async (req, res) => {
  const file = req.files?.document;
  // AI checks file size ✓
  if (file.size > 10 * 1024 * 1024) {
    return res.status(400).json({ error: 'File too large' });
  }
  // AI checks extension ✓
  if (!['pdf', 'doc', 'docx'].includes(file.name.split('.').pop())) {
    return res.status(400).json({ error: 'Invalid file type' });
  }
  // AI does NOT check MIME type ✗
  // AI does NOT check for embedded scripts in PDFs ✗
  // AI does NOT scan for malware ✗
  // AI saves to a predictable path ✗ (directory traversal risk)
  await file.mv(`/uploads/${file.name}`);  // ← Path injection possible

});

How we prevent it: Every security-sensitive path (authentication, authorization, file handling, data access, input validation, and payment processing) gets a manual security review using our internal checklist. AI generates the first draft. A human reviews it against a security threat model specific to that feature.

Mistake 4: The Review Bottleneck

What it is: AI generates code faster than humans can review it, creating a quality bottleneck that teams solve by lowering review standards.

Before AI, a developer might produce 1-2 PRs per day. The team could review each PR thoroughly. With AI, a developer might produce 4-6 PRs per day. The review queue backs up. And when the review queue backs up, one of two things happens:

  1. Reviews get rushed (someone skims the diff and approves)
  2. Reviews get skipped (“the tests pass, it is fine”)

Both lead to the same outcome: production code that nobody has carefully examined.

How we see it manifest: We looked at PR review times across our projects. Before AI adoption, the average review had 3-4 substantive comments. After AI adoption, the average dropped to 1-2 comments. The code was not getting better. The reviews were getting worse.

How we prevent it:

  1. PRs have a maximum size. No PR should change more than 300 lines. If an AI generates a 500-line change, it gets split into smaller, reviewable PRs.

  2. Review time is sacred. We schedule 1 hour per day specifically for code review. It is not optional. It does not get bumped for “urgent” tasks (which are rarely as urgent as they seem).

  3. AI-generated code gets flagged. Our PRs include a note indicating which files were AI-generated. This tells reviewers to apply extra scrutiny to those files.

  4. The “explain it” rule. During review, the author must be able to explain any AI-generated code. If they cannot, the PR goes back for revision, even if the code is correct.

Mistake 5: The Frankenstein Architecture

What it is: Over time, AI-generated code from different prompting sessions creates an inconsistent, layered architecture where different parts of the system follow different patterns.

This happens because each AI interaction is independent. When you ask Claude Code to build the user module on Monday, it might use Repository pattern. When you ask it to build the product module on Wednesday, it might use Active Record pattern. Both work correctly in isolation. Together, they create a codebase where developers never know which pattern to follow.

A real example: One client project had three different state management approaches in the same React application:

  • Local component state (from the original manual code)
  • Zustand stores (from an AI session in January)
  • React Context (from a different AI session in February)

All three worked. None of them shared state. The data flow was incomprehensible.

How we prevent it:

We maintain an Architecture Decision Record (ADR) file in every project:

# Architecture Decisions

## State Management
Decision: Use Zustand for all shared state.
Date: 2025-11-15
Rationale: Zustand offers simplicity, TypeScript support, and
devtools integration. React Context is too verbose for our needs.
Constraint: No new Context providers without team discussion.

## Data Access
Decision: Repository pattern via src/lib/data/ directory.
Date: 2025-11-15
Rationale: Centralizes database queries, makes testing easier.
Constraint: Components and API routes NEVER query the database
directly.

## Error Handling
Decision: Custom AppError class hierarchy.
Date: 2025-11-20
Rationale: Consistent error responses, proper HTTP status codes.
Constraint: All thrown errors must be instances of AppError.

This ADR file is included in the CLAUDE.md context so that every AI session knows the existing architectural decisions. It reduces architectural inconsistency by roughly 70%.

Mistake 6: The False Productivity Trap

What it is: AI-generated code ships fast but creates downstream costs that are not measured, leading to a false sense of productivity.

This is the most dangerous mistake because it feels like progress. The team is shipping features at 2x speed! Velocity metrics are up 40%! The sprint board is clearing faster than ever!

But here is what the velocity metrics do not capture:

  • The 3-hour debugging session when an AI-generated API returns incorrect data on edge cases
  • The 2-day refactoring sprint when you discover three different implementations of the same business rule
  • The security incident when an AI-generated endpoint is exploited because nobody audited the input validation
  • The customer support tickets from users who encounter inconsistent behavior across different parts of the application

The real math: If a feature takes 4 hours to generate with AI but creates 6 hours of downstream debugging and maintenance, you have not saved time. You have borrowed it.

How we prevent it: We track not just “time to ship” but “total cost of ownership” for features. This includes post-ship bug fixes, support tickets, and refactoring time attributed to that feature. When we see a pattern of high downstream costs from AI-generated features, we increase the review rigor for that part of the codebase.

Mistake 7: The Skill Erosion Problem

What it is: Developers who rely heavily on AI tools gradually lose the ability to write, debug, and reason about code independently.

We have seen this happen in real time on our team. A developer who used to trace through complex async code to find race conditions started asking Claude Code to find them instead. When Claude Code could not find the issue (because it required understanding the interaction between two services the AI could not see simultaneously), the developer struggled to do it manually, a skill they had just 6 months earlier.

This is not hypothetical or theoretical. It is happening across the industry. A developer who cannot debug without AI assistance is a liability, not an asset, because AI tools fail (context limits, hallucinations, system boundaries they cannot see) and when they fail, someone needs to step in with real understanding.

How we prevent it:

  1. 30% rule for juniors. Junior developers do at least 30% of their work without AI assistance, specifically the work that builds skills (complex debugging, writing algorithms, designing data structures).

  2. Monthly “unplugged” sessions. Once a month, the team works on a feature or bug fix with all AI tools disabled. This is uncomfortable. That is the point. The discomfort is where skill development happens.

  3. Teaching by doing. When a developer asks AI to explain code, we follow up by asking them to explain it back to a teammate. Active recall builds understanding; passive reading does not.

The Meta-Problem: Most Teams Do Not Know They Have These Problems

The most dangerous aspect of AI coding mistakes is that they are invisible in the short term. The code works. The tests pass. The features ship. The velocity metrics look great.

The problems only become visible when:

  • You need to debug a production issue in AI-generated code nobody understands
  • You need to onboard a new developer and they cannot make sense of the architecture
  • You need to add a feature and discover that the existing code has three different patterns for the same thing
  • A security audit reveals systematic vulnerabilities in your input validation

By the time you notice, the technical debt has compounded for months.

How to Start Fixing This

If you are using AI coding tools (and you should be, the productivity gains are real), here is our prioritized list of actions:

Week 1: Write a CLAUDE.md / project context file that documents your architectural decisions, existing utilities, and coding conventions. This single action prevents 50% of the problems described above.

Week 2: Implement the PR size limit (300 lines maximum) and schedule dedicated code review time.

Week 3: Add a security review checklist for all code that touches authentication, authorization, file handling, or payments. Review these paths manually regardless of whether AI or a human wrote the code.

Week 4: Start tracking total cost of ownership for features, not just time to ship. This gives you the data to identify where AI is creating hidden costs.

Ongoing: Watch for skill erosion. Talk to your developers about it openly. Create space for manual work that builds skills.

The goal is not to stop using AI coding tools. The goal is to use them effectively: getting the productivity benefits without accumulating the invisible debt that will cost you later.


Dealing with AI-Generated Technical Debt?

At CODERCOPS, we have audited and fixed AI-generated codebases for multiple clients. If you suspect your codebase has accumulated hidden technical debt from AI tools, or if you want to establish guardrails before problems develop, we can help.


This post is based on our experience across 11 client projects over 18 months. The patterns are consistent enough across projects that we are confident they represent industry-wide issues, not anomalies.

Sponsored

Enjoyed it? Pass it on.

Share this article.

Sponsored

The dispatch

Working notes from
the studio.

A short letter twice a month — what we shipped, what broke, and the AI tools earning their keep.

No spam, ever. Unsubscribe anytime.

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored