MSBA-304 · Lesson 9 of 12 · 21 min

Reviewing AI-generated code

▶ Video coming soon A video version of this lesson is on the way.

Friday, August 13, ended the third week with the habit that ties the whole course together: review. Every lesson so far had ended with "read it, run it, check it". This session made that systematic. Priya brought three real pull requests from the pilot: one written by a person with Copilot completions, one from agent mode and one from the coding agent. Each had a problem that tests had not caught. "The code looks good," she said, "and that is exactly the danger. Generated code is fluent. It reads like code written by someone who knows what they are doing, whether or not it is right." This lesson covers why AI-generated code needs its own review habits, a checklist for reviewers, invented APIs, security problems common in generated code, license and public code matches, and how to keep pull requests reviewable.

Why generated code needs particular care

Human mistakes and model mistakes differ. A person who does not know how to do something usually writes code that looks uncertain. A model produces code that looks confident whatever its quality, because it predicts likely text, not correct behavior. Generated code also tends to be longer than it needs to be, may follow patterns from public code that do not fit the project, and may use functions that sound right but do not exist. None of this makes it bad; much of it is good. It means review must check substance, not style.

Responsibility does not move to the tool. The first course covered GitHub's responsible AI guidance: the person who accepts and commits code owns it. In Tidewater's pull request template, the author ticks a box confirming they have read and understood every line, whoever or whatever wrote it. Reviewers review the code, not the author's claim.

A pull request on the left with a diff. On the right, six checkpoints arranged top to bottom with icons: intent (does it do what was asked?), existence (do all called functions and packages exist?), edges (what about unusual input?), security (input handling, secrets, permissions), license (any public code matches?), and size (can this be reviewed in one sitting?). Arrows link each checkpoint to a highlighted line in the diff.
Six checks for reviewing AI-generated code.

A reviewer's checklist

Priya built the team's checklist from GitHub's guidance and the pilot's own mistakes. It sits in the repository as a short document linked from the pull request template.

Tidewater's review checklist for AI-assisted pull requests.
CheckQuestionsPilot example
IntentDoes it do what the issue asked, no more and no less?The closures list sorted by name, not by date
ExistenceDo the functions, methods, packages and options exist in our versions?A call to a date method that does not exist in the standard library
Edge casesEmpty input, unusual characters, boundaries, time zonesAccented trail names lost in a slug function
SecurityIs input validated? Are queries parameterized? Are secrets kept out?A search query built by joining strings
License and originDid code referencing flag a match? Is a long block suspiciously polished?None in the pilot; filter set to block
TestsDo tests check the requirement, and would they fail if this broke?Tests that only checked a result existed
SizeCan this be reviewed carefully in one sitting?A 900-line agent change split into three

Invented functions and packages

Models sometimes call functions, methods, configuration options or packages that do not exist, because the name fits the pattern of real ones. In the pilot, a completion called a date method that sounded plausible but is not part of Python's standard library. The code failed at once when run, which is the good case. The worse case is a package name: a suggestion to install a package that does not exist on the package index, or worse, one whose name an attacker has registered to catch exactly this mistake. This is sometimes called slopsquatting. The defense is simple: never install a package because an assistant suggested it without checking that it is the real, maintained package the project means to use.

Worked problem

Checking a suggested dependency

Agent mode proposed adding a package called trail-geo-utils to requirements.txt to compute distances. What should the reviewer check before accepting it?

Show solution
  1. Does the package exist on the package index, under exactly that name?
  2. Who publishes it, how long has it existed, and is it maintained?
  3. Is a new dependency needed at all, or does the standard library already do this?
  4. Does adding it follow the rule in copilot-instructions.md about new libraries?

Answer: All four. In this case the distance calculation needed only the math module, so the dependency was removed, which also removed the risk.

Security problems in generated code

Models learn from large amounts of public code, and public code contains insecure patterns. Generated code is therefore prone to the same common weaknesses: building database queries by joining strings, trusting user input, writing secrets into code or logs, turning off certificate checks to make an error go away, or giving files and tokens broader permissions than needed. The GH-300 guide lists suggesting security improvements as a productivity skill; the other side of that skill is noticing when a suggestion makes security worse. GitHub's code scanning and secret scanning, covered in the GitHub Foundations courses, catch many of these automatically, and Copilot code review can be asked to look for them, but a human reviewer remains the last check.

The example below shows the pattern Priya found in the third pull request: a trail search that built its query by inserting the user's text into the SQL string. It worked in every test, because the tests used ordinary trail names. The fix uses a parameterized query, so the database treats the input as data rather than as part of the command.

Python Example · An unsafe query built by joining strings, and the safe parameterized version, tested on the same unusual input.
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE trails (name TEXT, status TEXT)")
db.executemany("INSERT INTO trails VALUES (?, ?)",
               [("Cove Path", "open"), ("Cape Trail", "closed"), ("Headland Loop Trail", "open")])

def search_unsafe(text):
    sql = "SELECT name FROM trails WHERE name LIKE '" + text + "'"
    return [r[0] for r in db.execute(sql)]

def search_safe(text):
    return [r[0] for r in db.execute("SELECT name FROM trails WHERE name LIKE ?", (text,))]

odd = "x' OR '1'='1"
print("unsafe, ordinary input:", search_unsafe("Cove Path"))
print("unsafe, odd input:     ", search_unsafe(odd))
print("safe, odd input:       ", search_safe(odd))
Output
unsafe, ordinary input: ['Cove Path']
unsafe, odd input:      ['Cove Path', 'Cape Trail', 'Headland Loop Trail']
safe, odd input:        []

What this code does

The example creates a small temporary table of three trails and two search functions. The unsafe one pastes the search text straight into the database command, so text containing quote marks can change what the command means; the safe one passes the text separately, so the database always treats it as a value to look for. With ordinary input both behave the same, but with the unusual input the unsafe version returns every trail in the table while the safe version correctly finds none, which is why the reviewer asked for the parameterized form.

This is a defensive example: the point is to recognize the unsafe pattern in review and replace it, not to exploit it. The general rule for reviewers is that any place where outside input meets a command, whether a database query, a shell command, a file path or a web page, deserves a close look.

Public code matches and licenses

The first course explained the setting that controls suggestions matching public code. Tidewater's organization blocks them, so Copilot does not show suggestions that match public code on GitHub closely. Where an organization allows them, code referencing shows when a suggestion matches public code, which repositories it appears in and the license detected, so the developer can decide whether the code can be used and how to attribute it. Matches are rare, but a reviewer seeing a long, polished block that does not look like the rest of the project should ask where it came from.

Handling public code matches in review.
SituationReviewer action
Public code filter set to blockMatching suggestions are not shown; still question unusual blocks
Filter allows matches; code referencing shows a permissive licenseCheck attribution requirements are met
Code referencing shows a license incompatible with the projectDo not accept; rewrite or use a different approach
A large block appears that the author cannot explainAsk the author to explain or rewrite it

When the code and its tests come from the same place

A particular trap appears when Copilot writes both the code and its tests in the same session, as agent mode often does. The tests and the code share the same understanding of the task, including any misunderstanding, so they agree with each other perfectly while both being wrong. The second pull request from the pilot showed this. Agent mode had written a slug function that turned trail names into web addresses and tests for it. The tests passed. Neither the code nor the tests handled accented letters, so Sentier de la Pointe became a slug with letters missing, and the trail's page could not be found. A reviewer reading only the green test results would never have noticed. Priya's rule for these pull requests was that the reviewer adds at least one test case of their own, chosen from knowledge of the real data rather than from the code. It takes a couple of minutes and breaks the circle. Lesson 4's advice applies here too: tests should come from the requirement, and when an agent writes both, the requirement it was given is the thing to check against.

Keeping pull requests reviewable

Assistants make it easy to produce a lot of code quickly, and review does not speed up in the same way. Research on code review has long suggested that reviewers find fewer problems per line as a change gets larger. Priya set a soft limit of about 400 changed lines per pull request, and asked authors to split larger agent work into a sequence. The first pull request of the pilot's 900-line agent change became three: the data model, the logic with its tests, and the template.

Worked problem

How long will this review take?

Tidewater's reviewers aim for about 200 lines an hour for careful review. A pull request changes 900 lines. How long would one careful review take, and how does splitting it into three help?

Show solution
  1. One review: 900 / 200 = 4.5 hours, too long for one sitting.
  2. Split: three pull requests of about 300 lines each, 1.5 hours each.
  3. Each smaller review can be done fresh, and each part can be merged or reverted on its own.

Answer: About four and a half hours as one review, or three sittings of an hour and a half. Same total time, much better attention.

Copilot code review as a first pass

Copilot code review, from the first course, can look at a pull request and leave comments. By default its review counts as a comment rather than an approval, so it never satisfies a required human review on its own. It is useful as a first pass: it often catches unclear names, missing error handling and simple bugs, which frees the human reviewer to think about intent and design. Instructions in .github/copilot-instructions.md and path-specific files guide what it looks for. Tidewater added a line asking it to flag string-built queries and new dependencies, the two problems the pilot had found.

In the pilot, about 45 in every 100 Copilot review comments were applied. The rest were dismissed as unhelpful or already handled. That is a reasonable rate for a first pass, and it shows why the human review stays: more than half of the comments needed a person to judge them.

Reviewing your own generated code

The most important review is the one the author does before opening the pull request. Dmitri built a short routine: read the full diff in the editor, not just the files he remembered changing; run the tests and the data check; search the diff for new imports and new dependencies; and write the pull request description himself, explaining why. If he could not explain a block of code in the description, he either asked chat to explain it until he could, or rewrote it.

Lena found the routine slowed her down for about two weeks and then sped her up, because her pull requests stopped coming back with questions. Reviewers noticed too: the average number of review rounds before merge on her pull requests fell from three to under two by the end of the pilot.

Common mistakes

  • MistakeApproving generated code because it looks clean and confident.

    InsteadCheck intent, existence, edges, security, license, tests and size.

  • MistakeInstalling a package because an assistant suggested it.

    InsteadConfirm it exists, is the intended package and is actually needed.

  • MistakeAccepting queries or commands built by joining strings with user input.

    InsteadUse parameterized queries and validated input.

  • MistakeTreating Copilot code review as the required approval.

    InsteadUse it as a first pass; a person approves.

  • MistakeOpening one huge pull request for a large agent change.

    InsteadSplit it into reviewable parts.

Summary and key terms

  • Generated code is fluent whether or not it is correct, so review must check substance.
  • The person who commits code owns it; reviewers review the code, not the author's claim.
  • Check for invented functions and packages; verify every new dependency.
  • Look closely wherever outside input meets a query, command, path or page; use parameterized queries.
  • Handle public code matches using the organization's filter setting and code referencing.
  • Keep pull requests small, use Copilot code review as a first pass, and review your own diff before asking others.
Hallucinated API
A function, method or option that a model invents because it fits the pattern of real ones.
Slopsquatting
Registering a package name that assistants tend to invent, so that people install it by mistake.
Parameterized query
A database query that passes input separately from the command, so input is treated as data.
Code referencing
A Copilot feature that shows public repositories and licenses when a suggestion matches public code.
First-pass review
An early, automated or quick review that catches simple problems before a person reviews in depth.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Why is a suggested package name a security risk?

    Show answer

    The package may not exist, or an attacker may have registered the invented name; it must be checked before installing.

  2. What makes a query safe against unusual input?

    Show answer

    Passing the input as a parameter rather than joining it into the query text.

  3. Why does Tidewater limit pull requests to about 400 changed lines?

    Show answer

    Reviewers find fewer problems per line in large changes; smaller ones get careful attention and can be reverted separately.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz