VCS
VIBE CODE STUDIO

Guides

Spotting code written by an AI

No single signal is proof. But some patterns recur often enough to steer a review — and others, widely shared, are worthless.

Updated 21 August 2026 · 5 min read

The question comes up in three concrete situations: in code review, when deciding how much attention a file deserves; in hiring, faced with a take-home test; and in audit, when inheriting a codebase whose origin nobody knows.

Let us be clear from the start: there is no reliable way to prove that code was generated. Tools claiming otherwise produce false positives in quantity, particularly on well-written code from experienced developers. What follows is not for accusing anyone, but for knowing where to look.

The signals that recur

The comment that restates the line

This is the most characteristic pattern. A developer comments the why, because the how is already in the code. A model comments the how, because it produces text alongside code without knowing what deserves explaining.

// Increment the counter
counter++;

// Loop over users
for (const user of users) {

// Return the result
return result;

Three comments of this kind in one file is the clearest signal available. A developer in a hurry writes no comments; they do not write useless ones.

Abnormal uniformity

Human code carries traces of its history: a function written on a Tuesday evening does not resemble Monday morning's. You find coexisting styles, an abbreviation here and a full word there, a forty-line function next to a three-line one.

Code generated in one pass is homogeneous throughout: same function lengths, same comment density, same naming approach everywhere. That regularity is pleasant to read and improbable in a human working over several weeks.

Decorative error handling

A very common pattern: the error is caught, then rethrown as-is or replaced by a generic message. The block exists, it looks reassuring, it does nothing.

try {
  return await fetchUser(id);
} catch (error) {
  console.error("An error occurred:", error);
  throw error;
}

A developer writing a try/catch usually has a reason: retry, fall back to a default, translate the error for the caller. When the block adds nothing, it often comes from an instruction like "add error handling" rather than from an actual need.

Suspicious completeness

No TODOs, no commented-out code, no half-finished function, every case handled uniformly. A real human codebase always carries traces of work in progress and deliberate compromise. Perfect tidiness in a first version says a lot.

Generic naming

A model names by the shape of the data, a human by its role in the business. So you get a lot of data, result, item, response, handleData, processItems — where a domain developer would have written invoice, unpaidLines or chaseCustomer.

This is also the most useful signal in practice, because it points at a genuine defect: this code does not speak the language of the business, so it will be harder to maintain whatever its origin.

Abstraction with no use

An interface implemented exactly once, a configuration parameter never used with anything but its default, a layer of indirection serving nothing. The model produces the complete shape of the design pattern, extension points nobody needs included.

The signals worth nothing

Several widely shared clues do not survive scrutiny. Taking them seriously leads to wrongly accusing people who simply write well.

  • Well-formatted or well-indented code — that is the auto-formatter, running on every serious project for a decade.
  • Emoji in commit messages — a team convention that long predates assistants.
  • Certain words in comments, or em dashes — these markers vary between models, change with every version, and mostly catch non-native speakers who write carefully.
  • The absence of typos — that indicates a spell checker, not a machine.
  • A large commit — that indicates a long branch or a migration, not necessarily generation.

What it changes in review

The point is not to settle the question of origin, but to adjust the review. Faced with code showing several of these patterns, three checks pay off more than anything else.

  1. Look for what is missing rather than what is wrong: input validation, rate limiting, real error cases. Generated code fails by omission, rarely by visible error.
  2. Verify that added dependencies genuinely exist, and have done for a while.
  3. Check the code against the business rules nobody wrote down. That is where generated code goes wrong most, because those rules appear in no corpus.

The hiring case

Trying to detect AI use in a take-home test is a losing battle, and probably the wrong question. A candidate who submits generated code they do not understand and one who submits generated code they reviewed, corrected and can defend do not have the same skills — but their code may be identical.

The only assessment that holds is conversational: ask why this approach rather than another, what happens when that input arrives, what would need to change to handle ten times the load. Those questions expose the difference immediately, and they stay relevant whatever tool was used.

Related reading

All guides