Home Features For Vibe Coders For Developers Pricing Documentation Services Contact
← Back to blog

AI-generated code keeps shipping the same five mistakes

I’ve been auditing codebases that lean heavily on Cursor, Claude Code, and Copilot. The bugs aren’t random. The same five categories show up over and over.

If you’re shipping AI-assisted code, scan for these before you push.

1. Try/catch swallowing every error

try {
  const data = await fetchUser(id);
  return data;
} catch (err) {
  console.error(err);
  return null;
}

This pattern is everywhere now. The AI writes it because it makes the code “more robust.” What it actually does is hide bugs. The function lies about its return type. The caller gets null and has no idea whether the user doesn’t exist or the database is on fire.

Catch errors only when you have a meaningful response. Otherwise, let them bubble.

2. Tokens and password hashes compared with ===

Timing-safe comparison is one of the things AI tools forget unless you specifically ask. They generate if (token === userToken) because that’s what most code does. For session tokens, password hashes, and HMAC signatures, you need crypto.timingSafeEqual or your language’s equivalent.

This bug is invisible until someone runs a timing attack against your auth endpoint. Then you’ll wish someone had reviewed it.

3. Unbounded retries on errors that won’t recover

async function fetchWithRetry(url, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    try { return await fetch(url); }
    catch { /* retry */ }
  }
}

Looks reasonable. Retries forever on a 404. Retries forever on DNS failure. Retries until the upstream service is fully unavailable, then keeps retrying because the AI didn’t distinguish 4xx from 5xx.

A real retry policy: only retry 5xx and network errors, with exponential backoff and a max delay cap.

4. SQL queries built with string concatenation

This shouldn’t be happening in 2026. It still is. The AI knows about SQL injection. It will sometimes write parameterized queries. It will also sometimes write:

const q = `SELECT * FROM users WHERE email = '${email}'`;

If the model has seen this pattern in training data, it will reproduce it under the right prompt. Always grep your generated code for backtick-quoted SQL with ${} interpolation before you ship.

5. useEffect dependency lies

useEffect(() => {
  fetchData(id);
}, []);

The empty dependency array is the AI’s favorite shortcut. The lint rule yells about it. The AI tells you to add a // eslint-disable-next-line comment. You add it. Now your component fetches stale data forever after id changes.

The fix is almost never to disable the lint rule. The fix is either to depend on id, or to refactor so the effect doesn’t need to run on id changes, usually by lifting state up.

The pattern

The pattern is the same across all five: AI tools optimize for code that compiles and looks reasonable. They don’t optimize for code that is correct under pressure. They have no concept of an attacker, a network partition, or a user who clicks buttons twice.

That’s still your job. Read your AI’s code with the same skepticism you’d give a junior contractor’s pull request.

Join the feed

Get feature releases and engineering updates delivered to your inbox.