How do I turn a huge AI-generated change into pull requests people can review?

Illustration of Ivo hugging a tall stack of printed pages to his chest with a sheepish smile while Noor, one hand on a door frame, rests her fingertips on top of the stack and gives him an amused sideways look.

TL;DR

A coding agent can hand you, in one afternoon, a change too large for anyone to review well. You can still make it reviewable. Sort every hunk by purpose first: mechanical edits, refactoring that keeps behaviour, new behaviour and unrelated cleanup. Then rebuild the change as a stack of pull requests in dependency order, where every commit builds and passes its tests, and show that the top of the stack differs from the original only where you intended. Reviewers then approve small steps without losing sight of the whole.

Sort the diff by purpose before you cut it

Cut a large diff into equal pieces and you get several small pull requests, each as confusing as the original. A useful cut follows purpose, because each kind of change asks the reviewer a different question.

Both public questions behind this article run into this. In 2022 a developer asked on Software Engineering Stack Exchange how to split one feature into reviewable steps. The meaningful tests needed the whole feature, and each review fix to an early step forced rebasing every branch above it. In December 2025 someone asked on Hacker News how to give feedback on co-workers' mostly AI-generated pull requests that were large and took the wrong approach. The second question adds what AI changes: the work can be finished before anyone has agreed on the approach.

Comparison

Kind of changeTypical content in an agent's diffWhat the reviewer must answerWhere it goes
MechanicalRenames, moved files, formatting, regenerated files, lock filesDid a tool produce it, and is it complete?Its own pull request near the bottom, with the command that produced it
Refactoring that keeps behaviourExtracted functions, a changed signature with every caller updatedDoes everything still do exactly what it did before?Below the behaviour that needs it, with existing tests unchanged
New or changed behaviourThe feature itself, new error handling, new dataIs this the right behaviour, and is it tested?One or more layers, each with its own tests
Unrelated cleanupEdits the request never asked forDo we want this at all?Out of the stack: a separate pull request, or dropped

git diff --stat shows how widely the change is spread. git diff -M reports renamed files as renames instead of a deletion plus an addition, and --color-moved colours code that only moved, so a relocated function stops looking new (git diff). The real work is in hunks that mix two kinds, such as a renamed function whose body also changed. git add -p lets you take part of a file one hunk at a time (git add).

Google's review guide keeps refactoring apart from feature work: moving and renaming a class is one change, fixing a bug in it another (Google: small changes). In an agent's diff, distrust the unrelated cleanup most. The article on stopping a coding agent changing unrelated code covers keeping it out of the next change.

Illustration seen from above of Noor and Ivo leaning on a wooden table laid out with four neat stacks of printed pages and one larger untidy pile, Noor holding one sheet up as Ivo looks at it in surprise.
Sorting the change by purpose comes before cutting it into pull requests.

Order the pieces so each step stands on its own

Each piece becomes a layer built on the one below. GitHub's documentation for stacked pull requests puts the rule simply: if code in one layer depends on code in another, the dependency must be in the same branch or a lower one (About stacked pull requests). A workable order:

  1. Mechanical changes at the bottom. They are quick to approve and their removal clears noise from every later diff.
  2. Refactoring that keeps behaviour next, with the existing tests unchanged and passing. If a test has to change in this layer, behaviour changed, and that edit belongs higher up.
  3. New interfaces together with at least one caller. Google's guide asks for a usage of a new API in the same change, so reviewers see how it is used and no unused API is merged.
  4. New behaviour, in as many layers as it has separable parts, each with its tests. If the feature must stay unreachable until the end, put it behind a flag and say so in the description.
  5. The switch that turns the feature on, and the end-to-end test, at the top.

Every layer must leave the system working; Google's guide calls this not breaking the build. The top Stack Exchange answer offers two alternatives to a chain: merge each task into main behind a feature toggle, leaving flag removal for later, or merge task branches into a feature branch, deferring integration review to one large final merge.

Rebuild the branch as a stack

Leave the agent's branch untouched as your reference and bring the work over one layer at a time. git restore --source=<branch> --staged --worktree -p offers each hunk from the agent's branch and copies accepted hunks into the index and the working tree, so the next commit contains them (git restore). With -p alone, the hunks reach only the working tree and need git add before the commit. Each layer gets a branch on the one before:

git rebase --exec replays the stack and runs the command after each commit, stopping at the first failure (git rebase). This exact sequence was run for this article on a small scratch repository with Git 2.54.0, and it stopped at layer one: the rename hunk changed an import, but one call to the old name sat inside a refactoring hunk. Finishing the rename at every call site before committing layer one made all three layers pass, and the final diff showed only the skipped cleanup.

When a reviewer asks for a change in layer one, commit the fix on that branch, switch to the top branch and run git rebase --update-refs pr1-mechanical. Name the upstream; without one, Git uses the branch's configured upstream. --update-refs moves the branches in between, but not one checked out in another worktree: in the scratch run, a layer open in a second worktree stayed on its old commit. Then rerun the --exec pass from main, which also tests the fix commit itself. There, all four commits passed after a fix to layer one, and the final diff held exactly the skipped cleanup and the review fix. A fix beside a later layer's lines can still cause a conflict, the pain the Stack Exchange asker described.

GitHub's stacked pull requests, a public preview at the time of writing, handle this bookkeeping on the server. The bottom one targets main, each above targets the branch below, and each shows only its layer's diff. Branch protection and default-branch checks apply to every pull request in the stack, and merging goes bottom up. All branches must be in one repository, and auto-merge is not supported (Merging stacked pull requests). Elsewhere, the chain works as ordinary pull requests on the branch below, rebased and retargeted by hand.

Prove the intermediate steps, then the whole

A split can lose code, add code, or leave a middle layer that only works with a later one. Check:

  • Every commit in the stack builds and passes its tests, not only the top one.
  • Mechanical and refactoring layers change no test expectations.
  • The diff between the top of the stack and the agent's branch holds only intended differences, such as dropped cleanup or review fixes, and the top pull request lists them.
  • The integration or end-to-end test for the whole feature runs on the top layer, because no lower layer can show the feature working.
  • After review fixes, git range-diff between the old and new stack shows which commits changed and how (git range-diff). Someone who approved a lower layer can then see what moved under that approval.
  • Continuous integration ran on every pull request in the stack, not just the bottom one.

Passing these checks shows the split kept the change intact, not that the change is right; that stays with the reviewers.

What each reviewer needs to see

The reviewer of layer two needs the shape of the whole stack without reviewing all of it. GitHub's engineering blog suggests reading the stack from the top down for context, then reviewing from the bottom up (GitHub Blog). Each layer's description can carry the same lines (an illustrative format):

The "not in this layer" line stops requests for what layer three already contains. Keep the full description, design and end-to-end evidence in the top pull request or the linked issue, and point every layer at it. GitHub's Copilot tutorial lets separate reviewers take layers in parallel, but reviews tightly integrated layers from the bottom so fixes travel up first (Stack AI-generated code in pull requests). Splitting is easier before the code exists. The article on splitting a large feature into tasks an AI coding agent can finish reliably covers that side.

Paqad does not split an existing branch; its part comes earlier. Each code request becomes one change with its own plan and evidence record, and new code work that comes up during a detour starts a separate change, so the first one does not keep growing. The workflow settings forbid unrelated refactors and tell the agent to ask before widening the scope. Hooks record the route on Claude Code and Codex CLI; on other tools these are instructions the agent can skip, and any effect on review time needs measuring. Paqad's routing and lanes page explains how changes are sized and when new work is split off.

Illustration of Noor and Ivo side by side on a wooden bench, Noor smiling as she marks a thin stapled set of pages with a pen, while Ivo, chin resting on his hand, watches her with a hopeful half smile.
A layer small enough to read in one sitting, with the purpose of the whole stack in view.

Frequently Asked Questions

Decide first whether splitting is cheaper than starting again. If the sort shows the approach itself is wrong, as the Hacker News asker suspected of their colleagues' pull requests, splitting it carefully only spreads a wrong design across several reviews. Agree on the approach, then have the agent rebuild one layer per branch, using the original as a reference.

Look hardest at lines changed for two reasons at once, such as a function that was renamed and also edited. Split it into two commits: first the rename with the old body, then the change to the body. With --color-moved, a block that is mostly coloured as moved but has plain lines inside it was moved and then edited.

Write each layer's one-line description before you build the layer. If you cannot describe a layer without mentioning a later one, the cut is in the wrong place. Refactoring layers rely on the existing tests, and new code gets unit tests at the layer's boundary.

Have at least one reviewer read the whole stack from the top before the bottom layer merges, so an objection to the design comes up before the foundation lands. If the design changes during review, plan the remaining layers again instead of patching the top one.

Their own layer, its place in the stack and a link to the whole. Name one person who owns the judgement of the complete feature and approves the top layer with the end-to-end evidence. A set of layer approvals does not add up to an approval of the feature unless someone is responsible for that.