<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: BZPRCHNY</title>
    <description>The latest articles on Forem by BZPRCHNY (@bzprchny).</description>
    <link>https://forem.com/bzprchny</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3812165%2Fa67eec57-9903-4739-810d-8082203ed61e.png</url>
      <title>Forem: BZPRCHNY</title>
      <link>https://forem.com/bzprchny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bzprchny"/>
    <language>en</language>
    <item>
      <title>5 Code Smells Only AI Creates (And How to Detect Them)</title>
      <dc:creator>BZPRCHNY</dc:creator>
      <pubDate>Sat, 07 Mar 2026 21:58:36 +0000</pubDate>
      <link>https://forem.com/bzprchny/5-code-smells-only-ai-creates-and-how-to-detect-them-1e3l</link>
      <guid>https://forem.com/bzprchny/5-code-smells-only-ai-creates-and-how-to-detect-them-1e3l</guid>
      <description>&lt;p&gt;I've mass-reviewed code from teams using Cursor, Copilot, and ChatGPT. After hundreds of PRs, I started noticing patterns — specific code smells that humans&lt;br&gt;
  rarely produce, but AI generates constantly.&lt;/p&gt;

&lt;p&gt;Here are the top 5, with real examples and how to catch them automatically.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Hallucinated Import&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI models confidently import packages that don't exist. Not typos — completely fabricated module names that sound plausible.&lt;/p&gt;

&lt;p&gt;import { validateSchema } from 'express-validator-utils';&lt;br&gt;
  // This package does not exist on npm&lt;/p&gt;

&lt;p&gt;from fastapi.middleware.logging import RequestLogger&lt;br&gt;
  # This module doesn't exist in FastAPI&lt;/p&gt;

&lt;p&gt;Why does this happen? LLMs predict the most likely next token. A package named express-validator-utils is statistically plausible. The model doesn't check&lt;br&gt;
  npm. It just guesses.&lt;/p&gt;

&lt;p&gt;How common: I've seen this in ~15% of AI-heavy PRs. It always passes code review because the name looks right.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Copy-Paste Clone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask AI to "add a similar endpoint" and it will duplicate 30 lines with one field changed. Humans refactor. AI copies.&lt;/p&gt;

&lt;p&gt;app.get('/users', async (req, res) =&amp;gt; {&lt;br&gt;
    const page = parseInt(req.query.page) || 1;&lt;br&gt;
    const limit = parseInt(req.query.limit) || 20;&lt;br&gt;
    const offset = (page - 1) * limit;&lt;br&gt;
    const results = await db.query(&lt;br&gt;
      'SELECT * FROM users LIMIT ? OFFSET ?', [limit, offset]&lt;br&gt;
    );&lt;br&gt;
    const total = await db.query('SELECT COUNT(*) as count FROM users');&lt;br&gt;
    res.json({ data: results, page, limit, total: total[0].count });&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;// Spot the difference...&lt;br&gt;
  app.get('/products', async (req, res) =&amp;gt; {&lt;br&gt;
    const page = parseInt(req.query.page) || 1;&lt;br&gt;
    const limit = parseInt(req.query.limit) || 20;&lt;br&gt;
    const offset = (page - 1) * limit;&lt;br&gt;
    const results = await db.query(&lt;br&gt;
      'SELECT * FROM products LIMIT ? OFFSET ?', [limit, offset]&lt;br&gt;
    );&lt;br&gt;
    const total = await db.query('SELECT COUNT(*) as count FROM products');&lt;br&gt;
    res.json({ data: results, page, limit, total: total[0].count });&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;Only the table name changed. A human would extract a paginate() helper. AI doesn't feel the pain of duplication — it has infinite patience for boilerplate.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Empty Catch Block&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI's favorite anti-pattern. Every try/catch gets an empty catch:&lt;/p&gt;

&lt;p&gt;try {&lt;br&gt;
    const data = await fetchUser(id);&lt;br&gt;
    return data;&lt;br&gt;
  } catch (e) {&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;try:&lt;br&gt;
      config = json.loads(raw)&lt;br&gt;
  except Exception:&lt;br&gt;
      pass&lt;/p&gt;

&lt;p&gt;Errors vanish. Bugs become invisible. Your app fails silently at 3 AM and the logs show nothing.&lt;/p&gt;

&lt;p&gt;AI does this because in training data, error handling is often abbreviated. The model learns that catch blocks are "boilerplate to fill in" and fills them&lt;br&gt;
  with nothing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Stale API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI training data has a cutoff. Models suggest APIs that were deprecated years ago:&lt;/p&gt;

&lt;p&gt;const buf = new Buffer('hello');          // deprecated since Node 6&lt;br&gt;
  const parsed = url.parse(req.url);        // deprecated since Node 11&lt;br&gt;
  app.use(bodyParser.json());               // built into Express since 4.16&lt;/p&gt;

&lt;p&gt;from distutils.core import setup          # removed in Python 3.12&lt;br&gt;
  loop = asyncio.get_event_loop()           # deprecated in Python 3.10&lt;/p&gt;

&lt;p&gt;These still work (usually), so they pass tests. But they're ticking time bombs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Overengineered Singleton&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask AI for a simple utility and you get an enterprise-grade AbstractSingletonProxyFactoryBean:&lt;/p&gt;

&lt;p&gt;class DatabaseConnection {&lt;br&gt;
    private static instance: DatabaseConnection;&lt;br&gt;
    private constructor() {}&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static getInstance(): DatabaseConnection {
  if (!DatabaseConnection.instance) {
    DatabaseConnection.instance = new DatabaseConnection();
  }
  return DatabaseConnection.instance;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;In a file that's imported once. By one function. AI loves design patterns — it's seen thousands of them in training data. It applies them whether they're&lt;br&gt;
  needed or not.&lt;/p&gt;

&lt;p&gt;How to Catch These Automatically&lt;/p&gt;

&lt;p&gt;I built vibe-check — an open-source CLI that detects these patterns. No AI, no API keys, no cloud. Just pattern matching tuned for AI-generated code.&lt;/p&gt;

&lt;p&gt;npx &lt;a class="mentioned-user" href="https://dev.to/bzprchny"&gt;@bzprchny&lt;/a&gt;/vibe-check .&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✖ error  Hallucinated import: "express-validator-utils" not found
         src/middleware.ts:3

⚠ warn   Stale API: new Buffer() → use Buffer.from()
         src/utils.ts:14

⚠ warn   Copy-paste clone detected (18 similar lines)
         src/routes/users.ts:10 ↔ src/routes/products.ts:10

● info   Empty catch block swallows errors silently
         src/db.ts:42

Vibe Score: 61/100 — Needs Work
4 issues · 127 files · 340ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It also has --fix to auto-repair simple issues (Buffer.from, console.log removal, empty catch fills).&lt;/p&gt;

&lt;p&gt;GitHub Action&lt;/p&gt;

&lt;p&gt;Add it to your CI in 30 seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uses: BZPRCHNY/&lt;a href="mailto:vibe-check@v0.1.0"&gt;vibe-check@v0.1.0&lt;/a&gt;
with:
  preset: strict
  fail-on: error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every PR gets a Vibe Score in the job summary.&lt;/p&gt;

&lt;p&gt;What it checks (22 rules)&lt;/p&gt;

&lt;p&gt;┌─────────────────┬─────────────────────────────────────────────────┐&lt;br&gt;
  │    Category     │                      Rules                      │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Hallucinations  │ Fake imports, non-existent packages             │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Copypasta       │ Cross-file clone detection                      │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Dead code       │ Console.log leftovers, empty catches            │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Stale APIs      │ Deprecated Node/Python/Express/React APIs       │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Overengineering │ Unnecessary singletons, abstractions, factories │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Security        │ Hardcoded secrets, missing env validation       │&lt;br&gt;
  ├─────────────────┼─────────────────────────────────────────────────┤&lt;br&gt;
  │ Dependencies    │ Missing packages, phantom dependencies          │&lt;br&gt;
  └─────────────────┴─────────────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;Configuration&lt;/p&gt;

&lt;p&gt;Drop a .vibecheckrc in your repo:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
    "preset": "strict",&lt;br&gt;
    "disable": ["console-leftover"],&lt;br&gt;
    "exclude": ["legacy/**"]&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;The Bigger Picture&lt;/p&gt;

&lt;p&gt;AI coding tools are incredible. I use them daily. But they have blind spots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No runtime context — AI doesn't know your Node version or installed packages&lt;/li&gt;
&lt;li&gt;No project memory — it can't feel the pain of duplicated code across files&lt;/li&gt;
&lt;li&gt;Training data lag — it suggests what was popular, not what's current&lt;/li&gt;
&lt;li&gt;Pattern addiction — it applies design patterns reflexively, not intentionally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Linting for these patterns is the equivalent of spell-check for AI code. You wouldn't ship a document without spell-check. Don't ship AI code without a vibe&lt;br&gt;
  check.&lt;/p&gt;




&lt;p&gt;GitHub: &lt;a href="https://github.com/BZPRCHNY/vibe-check" rel="noopener noreferrer"&gt;https://github.com/BZPRCHNY/vibe-check&lt;/a&gt;&lt;br&gt;
  npm: npm i -g &lt;a class="mentioned-user" href="https://dev.to/bzprchny"&gt;@bzprchny&lt;/a&gt;/vibe-check&lt;br&gt;
  Try it now: npx &lt;a class="mentioned-user" href="https://dev.to/bzprchny"&gt;@bzprchny&lt;/a&gt;/vibe-check .&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
