# Code tells and leftovers

> Clean the over-defended, over-commented residue agents leave in UI code, and catch the dev tools, routes, logs and secrets that must never ship.

Module: Spotting AI-default UI · Level: intermediate · 8 min · https://school.agents.org.in/design/tells/code-and-leaks

An agent wrote this component. It compiles, the tests pass, and the page works. Tap every line that gives away who wrote it.

*[Interactive demo: Seven code tells in sixteen lines. The default fix for almost all of them is deletion.]*

Nobody flags generated code for one bad line. They recognize the accumulation: a comment narrating every statement, a try-catch around code that can't throw, a fallback to an empty array, a helper that already exists two files over.

There is a reason. Models learned from places where code that *runs in isolation* wins, so they over-defend, over-comment and over-scaffold. Everything that keeps a snippet alive on its own makes it noise inside a real codebase. These are code tells (A habit of generated code: comments that narrate each line, try-catch around code that can't fail, fallbacks to empty arrays, 'as any', names like fetchDataV2. It runs, but it hides intent and real failures).

## The pass, in order

Scan for comments, then defense, then types, names, structure, logging, tests and scope. **Delete first**; rewrite only where behavior must survive.

**Comments.** One test: does it say something the code can't? A constraint, a unit, a tradeoff, the bug it works around: keep. Narration (`// increment the counter`), notes to a reviewer (`// Updated to use the new API`), section dividers, commented-out code: delete.

**Defensive theater.**

- Null checks against states the types already rule out. Fix the type once.
- Catch, log, rethrow. Empty catches. `catch (e) { console.error(e) }` that carries on as if nothing happened.
- **The silent fallback (Code that quietly substitutes a default when something fails, like catching a failed request and returning an empty list. The page shows 'No invoices' when the server is actually down) is the most dangerous item on the list.** `catch { return [] }` turns a broken API into "No invoices yet". Tests stay green while the main path is broken.

When removing a cast, catch or fallback forces a choice, choose the **loud failure** (a thrown error, a failed build, a visible error state) over the quiet wrong answer.

**Types and names.** `as any`, `@ts-ignore` and a `!` that silences instead of fixes: the compiler error was information. `data`, `result`, `item` say nothing; `unpaidInvoices` does. `fetchDataV2`, `EnhancedButton`, `utils-new.ts`: replace the old thing and take its name.

**Structure.** One-call wrappers, an options object with one caller, an interface with one implementation, a duplicate helper the model didn't search for, `TODO: implement error handling`.

**Logs and tests.** `console.log("here")` and `✅ Successfully initialized!` are debug leftovers. Never delete, skip or weaken a test to make it pass, and be wary of a test that recomputes the implementation's own math: it passes when both are wrong.

  **Before (avoid):**

```ts
// Fetch the data
fetch("/api/invoices")
  .then((res) => res.json())
  .then((result) =>
    setData(result))
  .catch(() => setData([]));
```

  **After (do this):**

```ts
const res =
  await fetch("/api/invoices");
if (!res.ok)
  throw new Error(
    `Invoices ${res.status}`);
setInvoices(await res.json());
```

*(Match the room. A codebase that is dense with comments by convention isn't slop; rewriting it to your taste is a drive-by change.)*

Keep the diff to what the task needed, and propose unrelated cleanup separately.

## Leftovers and leaks

The second half is worse, because none of it shows up as a bug. The app works. It just ships an inspector panel to every visitor, lets search engines index `/lab`, and prints the session to the console.

*[Interactive demo: Six things that work fine and must not ship.]*

The rule: **find everything, gate what you can't prove is dead, delete only what is provably dead.**

| Leftover | Why it blocks | Fix |
|---|---|---|
| Dev panels (Leva, Tweakpane, dat.gui), devtools, grid overlays | A hidden panel still ships its bytes and feeds its defaults into the UI | Gate on development and load with a dynamic import; tuned values go in a config file |
| Scaffolding routes (A page made for development, like /lab, /test or /playground. In production it gets indexed by search engines, often skips login, and shows unfinished work): `/lab`, `/test`, `/playground`, `/debug` | Indexed, often skip auth, show unfinished work | Not-found in production; robots.txt only as a second layer |
| `console.log` of a user, session or payment response | It is a data leak | Remove it and say so; keep real `console.error` in error boundaries |
| A secret in a public env var (A configuration value whose name starts with a prefix like NEXT_PUBLIC_ or VITE_, which gets bundled into the code every visitor downloads. A secret key there is readable by anyone) | `NEXT_PUBLIC_` and `VITE_` values are bundled into the browser | Report the **name**, never the value, and put "rotate this now" at the top |
| `localhost` or staging hosts in source | Breaks or misroutes in production | Read from configuration |
| A TODO on auth, payment, permission or deletion | "check ownership here" may mean anyone can delete anything | Quote it verbatim; it is probably the most important finding |

Also hunt `.only(` and `.skip(` in tests, fresh `@ts-ignore`, and env files tracked by git.

> **Two fixes that aren't.** A hidden panel and a robots.txt line both look like fixes. Neither is: the first still drives values and ships the library, and the second is a polite request that leaves the route open to anyone with the URL.

**Code tells and leaks pass (paste into your agent):**

```text
Review this diff for code tells and leftovers. Default fix is deletion.
- Delete narrating comments, reviewer notes, commented-out code, console.log debug lines.
- Remove catch-log-rethrow, empty catches and silent fallbacks (catch → [] or ?? default on a failed fetch); fail loudly or show an error state. Check response.ok.
- Replace any/as unknown/@ts-ignore with real types; rename data/result/item and V2 names.
- Hunt: dev panels not behind a dynamic import + dev gate, /lab /test /playground routes, logs of user/session data, secret-looking NEXT_PUBLIC_/VITE_ names (report names only), localhost URLs, TODOs on auth/payment/delete paths (quote verbatim).
Match the repo's existing style; keep the diff to the task.
```

**Check yourself:** Which line is the most dangerous to leave in?

- [ ] // Fetch the invoices
- [ ] const [data, setData] = useState([])
- [x] .catch(() => setData([]))
- [ ] import { useEffect } from "react"

*Why:* A silent fallback turns a broken API into an empty list. The page lies ('No invoices') and tests stay green while the real path is broken.

## In the skill

- https://school.agents.org.in/skill/anti-patterns/code-tells.md
- https://school.agents.org.in/skill/anti-patterns/leftovers-and-leaks.md
