The productivity gain from AI coding assistants is real. Developers are shipping features in hours that used to take days, and the volume of code being written has increased dramatically. But that speed comes with a hidden cost: the bugs and vulnerabilities that AI code introduces are different from the ones you would catch in a normal code review.
Human developers make mistakes that are usually tied to misunderstanding requirements or forgetting edge cases in the domain they know well. AI-generated code has a different failure profile. It optimizes hard for the happy path, it reproduces patterns from its training data without understanding why those patterns exist, and it has no awareness of the production environment your code will actually run in.
The six failure modes below show up repeatedly across AI-assisted codebases. None of them are exotic. They are predictable, and that means you can build a review process that catches them before your users find them instead.
The Six Failure Modes
1. Security Gaps
AI models are trained on vast amounts of public code, and a lot of public code has poor security practices. The result is that AI-generated code regularly produces hardcoded secrets, wildcard CORS policies, and endpoints with no input validation. These are not random mistakes. They are the default when the model has not been given explicit constraints.
A concrete example: ask an AI to scaffold an Express API and it will often call app.use(cors()) with no configuration, which opens every endpoint to requests from any origin. Ask it to build a Next.js route that calls an external API and it may hard-code the API key directly in the handler. Both of these look like working code and will pass a quick functional test. Neither is safe to ship.
The pattern to watch for: any time AI generates code that touches credentials, external services, or accepts user input, treat it as unreviewed from a security standpoint. Check that secrets come from environment variables, that CORS is locked to specific origins, and that user input is validated before it reaches your database or business logic.
2. No Error Handling
AI writes for the scenario where everything works. The function gets called with valid arguments, the network request succeeds, the database returns a result. It rarely accounts for what should happen when it doesn't. Missing error handling does not show up in a happy-path demo, but it surfaces the moment a real user does something unexpected or a dependency is temporarily unavailable.
Consider AI-generated code that fetches data from a third-party API and renders it. The fetch call will typically have no try/catch, no handling for non-200 responses, and no fallback for when the API is rate-limiting you or down for maintenance. In development this works fine. In production, one bad response crashes the page or silently returns nothing to the user with no indication of what went wrong.
When reviewing AI-generated code, scan for every async operation, every external call, and every place where the code assumes a value exists. Each of those is a place where an unhandled failure can surface to your users or cause silent data corruption.
3. Scalability Blind Spots
Code that works correctly with ten users can fall apart with a thousand. AI-generated code is almost always written for correctness at small scale, not for the performance characteristics that emerge under real load. The most common pattern is the N+1 query: a loop that issues a separate database query for each item it is iterating over, which works fine with a handful of records and becomes catastrophically slow with hundreds.
Beyond N+1 queries, AI routinely generates synchronous code for operations that should be asynchronous, skips pagination on queries that could return unbounded result sets, and omits rate limiting on endpoints that call paid external APIs. Ask an AI to generate a route that sends an email notification and it will often do the SMTP call inline in the request handler, blocking the response until the email goes out, rather than queuing it as a background job.
These issues are hard to see in code review without actively looking for them. For database code, check that related records are fetched with joins or includes rather than in a loop. For external calls, check whether they belong in a background queue. For any endpoint that could be called at high frequency, check whether rate limiting or caching belongs in front of it.
4. Dependency Risks
When AI generates code that needs a capability it does not want to implement from scratch, it will reach for an npm package. The problem is that it pulls from its training data, which may be months or years out of date. It may suggest packages with known vulnerabilities in the version it recommends, packages that have been deprecated in favor of something maintained, or packages that are simply unnecessary given what the language or your existing dependencies already provide.
A specific case: AI frequently recommends momentfor date manipulation, a library the maintainers themselves describe as a legacy project in maintenance mode. The recommended replacement, date-fns or the native Intl API, is almost always the better choice. The AI is not wrong that moment solves the problem. It is just drawing on patterns from code that predates the current best practices.
After AI generates code that imports new packages, run npm audit before committing. Check that each new dependency is actively maintained and is actually necessary. If the AI is pulling in a package to do something that a built-in API or an existing dependency already handles, remove the redundant package.
5. Maintainability Problems
AI-generated code is optimized for doing what you asked in the current prompt. It is not optimizing for the person who will read that code six months from now, or for how it fits into the patterns and conventions the rest of your codebase uses. The result is code that works but that does not belong, duplicates logic that already exists elsewhere, and leaves no trace of why the non-obvious decisions were made.
Paste a large block of AI-generated logic into a codebase that uses a specific error handling pattern, a particular approach to data fetching, or a consistent naming convention, and the new code will usually ignore all of it. If you have a custom hook for data fetching, the AI will fetch directly in the component. If you have a shared validation utility, the AI will write the validation inline. The code is correct but inconsistent, and inconsistency compounds into debt faster than most other problems.
The review for this is straightforward but requires context: does this code use the same patterns as the surrounding codebase? Does it reuse existing utilities or duplicate them? Are the non-obvious parts explained? AI is particularly prone to writing complex logic, like regex patterns or intricate conditionals, with no comment explaining what they are doing and why.
6. False Confidence from Passing Tests
AI is good at writing tests. This sounds like a benefit, and it is, as long as you understand what those tests actually cover. The tests AI generates by default are happy-path tests. They call the function with valid input, assert that it returns the expected output, and pass. What they typically do not test is what happens with invalid input, with edge cases at the boundaries of the expected range, with missing or null values, or with unexpected network or database failures.
The danger is that a test suite full of green AI-generated tests creates confidence that is not warranted. You have 80% coverage and every test passes, but the 20% that is not covered is exactly the failure-mode territory described in the sections above: the error paths, the edge cases, the things that go wrong under real-world conditions.
When reviewing AI-generated tests, ask whether each test could fail if the code were subtly broken. If a test would pass even with a bug in the error handling path, that test is not testing error handling. Add explicit tests for invalid inputs, missing values, and failure scenarios. A test suite that only tests success paths is a liability that makes the codebase feel safer than it is.
How to Catch These Before Production
A focused review process catches the majority of these issues before they ship. The checklist below is specific to AI-generated code and takes under fifteen minutes to run through on a typical feature branch.
- Check every place a secret or credential is used and confirm it comes from an environment variable, not a string literal
- Review every API route for authentication: does it verify who is making the request before doing anything?
- Trace every piece of user input from entry point to database or external call and confirm it is validated with a schema
- Check CORS configuration: is the allowed origin locked to your domain, or is it a wildcard?
- Look for database queries inside loops and replace them with batch queries or joins
- Search for async operations that are missing try/catch blocks or error fallbacks
- Audit new npm packages: run
npm auditand verify each package is maintained and necessary - Check that the new code follows the same patterns and reuses the same utilities as the rest of the codebase
- Review the test coverage for error paths, not just the happy path
- Look for complex logic, regex, or conditionals that have no comment explaining the intent
Automated tools can accelerate this. GitDoctor scans your GitHub repository and flags many of these issues automatically, including exposed secrets, missing auth checks, injection risks, and dependency vulnerabilities, so you can focus your manual review time on the patterns that require human judgment.
The goal is not to stop using AI to write code. The goal is to review it with the specific failure modes in mind so the review is targeted rather than a general read-through that misses the predictable problems.
Ship Fast, But Know What to Check
Shipping fast with AI assistance is a real competitive advantage. The teams that get the most out of it are not the ones who use it most aggressively and hope for the best. They are the ones who understand how AI code fails and have built a review habit that catches those failures before they reach users. That discipline is what separates a sustainable velocity from a debt spiral that eventually forces you to slow down and fix everything you shipped too quickly.