Skip to main content
New aislop v0.8.3: MCP server, TypeScript typecheck engine, hallucinated-import detector + 8 new multi-language patterns. Read more →
Reference

AI Slop patterns.

AI coding agents leave specific tells behind: comments that restate the next line, as any casts, swallowed errors, dead imports, todo stubs, generic variable names. We named them. aislop catches them deterministically — across 14 patterns, in 8+ languages, on every edit.

$ npx aislop scan 3 of 14 auto-fixable

A multi-line comment block that walks through what the function does in prose. The signature already says it; the prose is decorative.

Bad — what an agent ships
// This function takes a user ID and a list of orders,
// filters the orders to only those belonging to the user,
// then sums the total amount across them and returns it.
function sumUserOrders(userId: string, orders: Order[]): number {
  return orders
    .filter(o => o.userId === userId)
    .reduce((sum, o) => sum + o.amount, 0)
}
Good — what aislop hands back
function sumUserOrders(userId: string, orders: Order[]): number {
  return orders
    .filter(o => o.userId === userId)
    .reduce((sum, o) => sum + o.amount, 0)
}
Why an AI agent produces this

Agents are trained to "explain" code. They pad function bodies with explanatory preambles even when the signature, parameter names, and operator chain already tell the same story.

A single-line comment that restates exactly what the next line of code does. Removing it changes nothing for the reader.

Bad — what an agent ships
// Get the user's name
const name = user.name

// Loop through items
for (const item of items) {
  total += item.price
}
Good — what aislop hands back
const name = user.name

for (const item of items) {
  total += item.price
}
Why an AI agent produces this

Agents narrate every step when generating code, then leave the narration in. The reader gets two copies of the same information.

A try/catch where the catch block is empty or only contains a comment — failures vanish silently with no log, no rethrow, no recovery.

Bad — what an agent ships
try {
  await processPayment(order)
} catch (e) {
  // ignore
}
Good — what aislop hands back
try {
  await processPayment(order)
} catch (e) {
  log.error('payment failed', { orderId: order.id, error: e })
  throw e // or: return failure result, depending on caller contract
}
Why an AI agent produces this

Agents wrap risky calls in try/catch to "be safe", without thinking about what should happen on failure. The result is an outage that takes hours to diagnose because there's no log line anywhere.

An `as any` cast — bypasses TypeScript's checker entirely. Every guarantee from the type system is gone for that value forward.

Bad — what an agent ships
const user = res.data as any
user.profile.preferences.theme = 'dark' // any deep access, no checks
Good — what aislop hands back
// Validate at the boundary, then trust the type:
const user = UserSchema.parse(res.data)
user.profile.preferences.theme = 'dark' // typed all the way down
Why an AI agent produces this

Agents reach for `as any` when they can't easily derive the real shape. It silences the error in 5 seconds and quietly disables the feature you bought TypeScript for.

An `as unknown as X` cast — a double-cast escape hatch used when the compiler refuses a direct `as X`. Smuggles types through without validation.

Bad — what an agent ships
const user = data as unknown as User
Good — what aislop hands back
// Either: declare a parser and validate at runtime
const user = UserSchema.parse(data)
// Or: narrow honestly with type guards
if (isUser(data)) { /* data is User here */ }
Why an AI agent produces this

When TS rejects a single cast with "Type X is not assignable to Y", agents reach for the double cast as the next escape hatch. Same problem, more hidden.

A `@ts-ignore`, `@ts-expect-error`, or `@ts-nocheck` directive without a reason comment. Silences the type checker on that line forever.

Bad — what an agent ships
// @ts-ignore
mod.doSomething(payload)
Good — what aislop hands back
// @ts-expect-error mod's types lag behind v3 — tracked in #1234
mod.doSomething(payload)
Why an AI agent produces this

Agents silence type errors instead of solving them. Without a reason, the directive becomes load-bearing and nobody can safely remove it later.

A `// TODO` without an owner, ticket, or completion plan. The kind that survives years.

Bad — what an agent ships
// TODO: handle the retry case
return result
Good — what aislop hands back
// TODO(#1234): retry on 503 with exponential backoff — currently fail-fast
return result

// or just finish it now:
return retry(() => fetch(url), { retries: 3, backoff: 'exp' })
Why an AI agent produces this

Agents punt hard cases under a comment and move on. Ungated TODOs accumulate forever; nobody knows whether they're still relevant or already irrelevant.

Variables and parameters named `data`, `result`, `value`, `temp`, `obj`, `info`, `item`. Names that say "this is a noun" without saying which noun.

Bad — what an agent ships
function process(data: any) {
  const result = data.map((item: any) => item.value * 2)
  return result
}
Good — what aislop hands back
function doublePrices(products: Product[]): number[] {
  return products.map(p => p.price * 2)
}
Why an AI agent produces this

Without seeing how the value is used downstream, agents fall back to generic placeholders. The reader has to infer the role from context — every time.

An import that no remaining code references. Usually left over after a refactor or a deletion the agent forgot to follow through on.

Bad — what an agent ships
import { useEffect, useState, useMemo } from 'react'
import { formatDate } from '../utils/dates'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Good — what aislop hands back
import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Why an AI agent produces this

Agents add imports speculatively then refactor away the consumer; the import lingers because removing it requires re-reading the file end-to-end.

`console.log` (or `print`, in other languages) left in production code after debugging.

Bad — what an agent ships
function reconcile(orders: Order[]) {
  console.log('reconciling', orders.length)
  for (const o of orders) {
    console.log('processing', o.id, o)
    process(o)
  }
}
Good — what aislop hands back
function reconcile(orders: Order[]) {
  log.debug('reconciling', { count: orders.length })
  for (const o of orders) {
    process(o)
  }
}
Why an AI agent produces this

Agents trace execution with console.log during a session and forget to remove them. The logs end up shipped — noisy, untyped, ungrep-able.

A function with no body — usually a placeholder the agent stubbed to make a signature compile and forgot to fill in.

Bad — what an agent ships
function handleSubmit(values: FormValues) {
}

export function Form() {
  return <form onSubmit={handleSubmit}>...</form>
}
Good — what aislop hands back
// either implement it
function handleSubmit(values: FormValues) {
  return saveDraft(values)
}

// or remove it and let the form be uncontrolled until you decide
Why an AI agent produces this

When asked to scaffold, agents stub functions to make types resolve, then forget to come back. The empty body ships and silently no-ops.

Code after a `return`, `throw`, `break`, or `continue` that can never execute.

Bad — what an agent ships
function getStatus(order: Order): string {
  if (order.cancelled) {
    return 'cancelled'
    log.info('cancelled order seen') // never runs
  }
  return order.status
}
Good — what aislop hands back
function getStatus(order: Order): string {
  if (order.cancelled) {
    log.info('cancelled order seen', { id: order.id })
    return 'cancelled'
  }
  return order.status
}
Why an AI agent produces this

When agents refactor control flow they sometimes leave statements stranded after a return. The code looks like it does something; it does nothing.

An `if` or `while` whose condition is a constant — `if (true)`, `while (false)`, `if (1)` — leaving a dead branch in the source.

Bad — what an agent ships
if (true) {
  doStuff()
} else {
  // never runs
  fallback()
}
Good — what aislop hands back
// just call it
doStuff()

// or, if there's a real toggle, make it real:
if (config.featureEnabled) {
  doStuff()
} else {
  fallback()
}
Why an AI agent produces this

Agents leave debug toggles or temporary feature flags hard-coded after testing. Half the branch is unreachable; the reader has to figure out which half is real.

A function that exists only to call another function with the same arguments — no transformation, no added value, just an extra layer.

Bad — what an agent ships
// In some/file.ts
export function getUser(id: string) {
  return userService.getUser(id)
}

// Callers:
import { getUser } from './some/file'
const user = await getUser(id)
Good — what aislop hands back
// Just import directly:
import { userService } from './services'
const user = await userService.getUser(id)
Why an AI agent produces this

Agents introduce wrappers preemptively "in case we need to extend it later". They never need to. The wrapper hides the actual call, doubles the import surface, and adds zero value.

Score your repo.

One command. 0–100 score. Every pattern above, every change. Auto-fix what's mechanical, hand off the rest with full context.