Menu

Code Review Prompts: How to Review Code with AI

A good code review prompt says what to look for, asks for a severity on every finding, wants a concrete failing input and a diff, and tells the model to skip style. Then you check each finding, because some will be wrong.

Every prompt below is editable: change it, then open it in ChatGPT, Claude or another AI app.

A code review prompt should tell the model what to look for, how serious each finding is, and what proof to give. "Review this code" gets a friendly list of suggestions where a security hole sits between a docstring tip and a naming preference. A review prompt with a checklist, severity levels and a request for diffs gets a report you can act on, one finding at a time.

From "review this" to a checklist with severity

The code in both tabs has two real bugs. Compare how each reply presents them.

Can you review this code? def get_user(conn, username): cur = conn.cursor() cur.execute(f"SELECT id, email FROM users WHERE name = '{username}'") row = cur.fetchone() return {"id": row[0], "email": row[1]}
Try it
Example replyReplies vary between models and runs.

Overall the function is short and easy to follow. A few suggestions:

  • Add type hints, for example username: str, to make the signature clearer.
  • Add a docstring explaining what the function returns.
  • Use parameterized queries. Building SQL with f-strings can lead to SQL injection.
  • Handle missing users. fetchone() can return None.
  • Naming: fetch_user might describe the database access better.

The first reply found both bugs, and that is the problem with judging a review by what it mentions. The injection sits third in a list of five, softened to "can lead to", with no fix, next to a docstring tip. A reader skimming it would add type hints and move on.

The second prompt changed four things:

  • Scope. "Bugs, security and error handling only. Skip style" removes the noise. Style belongs to a linter and a formatter, which are faster and never disagree with themselves.
  • Severity. It forces the model to rank, and a ranked list tells you where to start.
  • A triggering input. "x' OR '1'='1" turns a vague risk into a demonstration you can run. It is also your best filter for false positives, as the last section shows.
  • Diffs. A small diff per finding can be applied or rejected on its own. A rewritten function mixes every fix with changes nobody asked for.

"If there is nothing at a severity level, do not invent something" matters more than it looks. Asked for a review, a model tends to produce findings even when there is little to find, and the weakest ones pad the list. Explicit permission to report nothing at a level cuts that padding.

Context the reviewer needs

A human reviewer knows what the code is for and where its inputs come from. The model knows only what you paste. "username comes straight from a login form" is the sentence that makes SQL injection a high-severity finding instead of a theoretical one. Useful context for a review:

  • Where the inputs come from: users, another internal service, a config file you control.
  • What calls the code and what it expects back.
  • The runtime: language version, framework, database driver. The fix for the injection above uses ? because sqlite3 uses that placeholder; some other drivers use %s.
  • What you care about most for this change: correctness, security, performance, or all three.

A reusable review prompt

This block turns the checklist into a template. Change the language, the focus and the code, and keep the rules. Switching off the constraints part is a quick way to see how much noise comes back without it.

Code review template
Fill in
Parts
You are a senior JavaScript reviewer. You report real problems and nothing else.
Review the code below, focusing on bugs and error handling.
This runs in the browser. `ids` is an array of numeric ids from our own API.
async function loadAll(ids) { const results = []; ids.forEach(async (id) => { const res = await fetch(`/api/items/${id}`); results.push(await res.json()); }); return results; }
For each finding: severity (high, medium or low), the line, an input or situation that triggers it, and a minimal diff. Most severe first.
No style, naming or formatting comments. If you are not sure a finding is real, say so and say what would confirm it. If you find nothing, say "No findings".
Try it
Example replyReplies vary between models and runs.

1. High: the function returns before any request finishes (line 3) forEach does not wait for async callbacks. loadAll returns the empty results array straight away, and the pushes happen later. Any caller gets [].

2. Medium: failed requests are treated as data (line 5) A 404 or 500 response is not an exception in fetch. A JSON error body is parsed and pushed as if it were an item, and a non-JSON body makes res.json() throw inside a callback nobody awaits.

Fix for both, which also keeps the results in the same order as ids:

 async function loadAll(ids) {
-  const results = [];
-  ids.forEach(async (id) => {
-    const res = await fetch(`/api/items/${id}`);
-    results.push(await res.json());
-  });
-  return results;
+  return Promise.all(
+    ids.map(async (id) => {
+      const res = await fetch(`/api/items/${id}`);
+      if (!res.ok) throw new Error(`Item ${id}: HTTP ${res.status}`);
+      return res.json();
+    })
+  );
 }

The role line sets a standard ("real problems and nothing else"), which is where role prompting helps most. If a program reads the review, for example a script that posts findings as comments on a pull request, ask for JSON with fixed fields instead of prose; structured output shows how.

Handling false positives

Some findings will be wrong. The model cannot see the caller that validates the input, the database constraint that makes a case impossible, or the reason a strange-looking line is intentional. It may also be wrong about how a library behaves; AI hallucination explains why a confident claim is not a checked one.

Test each finding against its triggering input. If you cannot produce an input that reaches the problem in your system, push back with the missing context instead of accepting the fix:

Challenge a finding
You flagged this function because `cents` might be undefined or a string: function formatPrice(cents) { return `$${(cents / 100).toFixed(2)}`; } It is only called from `renderCart`, which returns early unless `Number.isInteger(cents)` is true. Is this still a problem? If not, withdraw the finding.
Try it
Example replyReplies vary between models and runs.

With that check in place, no: formatPrice only ever receives an integer, so the finding does not apply and I withdraw it.

One thing the check does not cover: negative integers pass Number.isInteger, and formatPrice(-500) returns "$-5.00". If refunds or discounts can reach this function, you may want "-$5.00" instead. If they cannot, there is nothing to change.

A model that is shown new evidence should either withdraw the finding or explain why it still holds with that evidence in view. Both are useful. A model that simply agrees with every pushback is not reviewing, so judge the reply by its reasons, not by whether it agreed. For code the model wrote itself, run the review in a fresh conversation, so the review is not read through the same reasoning that produced the code. The same habits of scope, context and tests make the code better in the first place; see prompts for writing code.

Frequently Asked Questions

What is a good prompt for code review?

Name what to review for (bugs, security, error handling), ask for a severity on each finding, and require a concrete input that triggers the problem plus a diff that fixes it. Tell the model to skip formatting and naming unless asked. Give it the context a human reviewer would have: what the code is for and what calls it.

Can AI replace human code review?

No, but it is a useful first pass. It is fast, and it is good at common bug patterns such as unhandled None, missing awaits and SQL built from strings. It does not know your product's rules, the rest of the codebase or why a decision was made, and some of its findings will be wrong. Use it before a human review, not instead of one.

Why does an AI code review report problems that are not real?

The model sees only the code you pasted. If a value is validated by the caller, or a case can never happen in your system, the model cannot know that, so it flags the risk anyway. Asking for a concrete failing input filters many of these out: a finding with no realistic input that triggers it is usually a false positive.

Should I ask the AI to rewrite my code during a review?

Ask for small diffs, one per finding, instead of a rewritten file. A full rewrite mixes the fixes with changes nobody asked for, and you would have to review the rewrite too. Diffs let you accept or reject each finding on its own.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED