Code tells and leftovers
intermediate·8 min·Markdown for agents
What this lesson teaches
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.
An agent wrote this component. It compiles, the tests pass, and the page works. Tap every line that gives away who wrote it.
Tap any detail that a model would produce by default. Each find is numbered and explained here.
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 .
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 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
// Fetch the data
fetch("/api/invoices")
.then((res) => res.json())
.then((result) =>
setData(result))
.catch(() => setData([]));✓ After
const res =
await fetch("/api/invoices");
if (!res.ok)
throw new Error(
`Invoices ${res.status}`);
setInvoices(await res.json());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.
Tap any detail that a model would produce by default. Each find is numbered and explained here.
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 |
: /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 | 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.
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.
Quick check
Which line is the most dangerous to leave in?
In the skill
The nodes your agent reads when it does this work.
- Code tellsGenerated UI code is rarely buggy and still costs you — narrated comments, catch theater, silent fallbacks, casts that delete compiler errors, V2 names, one-call wrappers, dead scaffolding, celebratory logs, weakened tests, drive-by diffs. The default fix is deletion.
anti-patterns/code-tells - Leftovers and leaksWhat the build process leaves behind and the visitor sees — dev panels mounted for everyone, scaffolding routes indexed, debug logs of a session, secrets behind a public prefix, TODOs on the payment path. Gate what you can't prove is dead; report names, never values.
anti-patterns/leftovers-and-leaks
Connected lessons
What this lesson builds on and leads to, across every course.