Find Which AI Change Broke Your Build
An agent handed you one commit across thirty-one files and the build is red. How to split that commit into bisectable slices, run git bisect properly against agent history, read the culprit, and stop producing unbisectable commits in the first place.
Your build was green this morning. You ran an agent for forty minutes, it produced one commit touching thirty-one files, and now four tests fail. The fastest way to find which AI change broke your build is not to read the diff. It is to make the failure reproducible in one command, then use `git bisect` against the agent's own history at a finer granularity than the commit it gave you. That means splitting the giant commit first, which takes about ninety seconds and is the step most people skip. This walks through the whole sequence with real commands, and then covers how to stop producing unbisectable commits in the first place.
Start with a one-command reproduction
Bisecting is only useful if you can answer "is this revision broken" mechanically. Before touching history, get the failure down to a single command with a clean exit code.
# Narrow to the failing tests, not the whole suite
npx vitest run src/billing/invoice.test.ts --reporter=dot
echo $? # 1 when broken, 0 when fineTwo things to check here. First, that the command fails on your current HEAD and passes on the last commit you know was green. If it passes on both, you are chasing a flaky test, not a regression. Second, that it runs in under about twenty seconds. Bisect will run it roughly log2(N) times, and a two minute test command turns a five minute investigation into a coffee break.
If the failure only appears in a full-suite run, you have test pollution rather than a broken change, which is a different problem with a different fix.
Split the agent's commit into bisectable pieces
Here is the core problem. A human writing thirty-one files produces eight commits. An agent produces one. `git bisect` on a one-commit range tells you what you already know: that commit did it.
Soft-reset it and rebuild the history in slices that match the change's logical seams.
git log --oneline -3
# a91f2c4 (HEAD) feat: add invoice retry handling
# 7b30ef1 chore: bump deps
# 0c4d8aa fix: null guard on customer lookup <- last known green
git reset --soft 7b30ef1 # keep every change, drop the single commit
git status --short # see the 31 files as staged changesNow commit them in groups, cheapest and most independent first:
git reset # unstage everything
git add package.json package-lock.json && git commit -m "wip: deps"
git add src/lib/ && git commit -m "wip: shared helpers"
git add src/billing/ && git commit -m "wip: billing logic"
git add src/api/ && git commit -m "wip: api handlers"
git add -A && git commit -m "wip: remainder"The commit messages are throwaway, you will squash them at the end. What you are buying is five bisect points instead of one. In practice the "shared helpers" and "deps" slices are where surprises hide, because those are the changes the agent made in passing while working on something else. If you want the general version of this discipline, we cover it in how to use git with AI coding agents.
One caveat: intermediate slices may not compile. That is fine for bisect as long as your test command distinguishes "broken by this change" from "does not build yet". If a slice cannot build at all, mark it skipped rather than bad, which the next section covers.
Run the bisect
git bisect start
git bisect bad HEAD
git bisect good 0c4d8aaGit checks out a midpoint. Run your reproduction command, then tell git the result:
git bisect bad # this revision fails
git bisect good # this revision is fine
git bisect skip # cannot build or test here, try a neighbourBetter, hand it the command and walk away:
git bisect start HEAD 0c4d8aa
git bisect run npx vitest run src/billing/invoice.test.ts`git bisect run` treats exit code 0 as good, 1 to 124 as bad, and 125 as skip. That last one matters with agent commits. Wrap your command so a compile failure returns 125 instead of a plain failure:
#!/usr/bin/env bash
npm run build --silent || exit 125
npx vitest run src/billing/invoice.test.ts --reporter=dotSave it as `bisect.sh`, `chmod +x bisect.sh`, then `git bisect run ./bisect.sh`. Finish with `git bisect reset` to return to where you started.
Read the culprit slice properly
Bisect gives you a commit, not an explanation. Now the diff is worth reading, because it is one slice instead of thirty-one files.
git show --stat <culprit>
git show <culprit> -- src/lib/Three patterns account for most of what you will find in agent-authored regressions:
Pattern | What it looks like | Why the agent did it |
|---|---|---|
Signature drift | A shared helper gains or loses a parameter, and one of six call sites was not updated | The agent held the helper in context but not every caller |
Silent behaviour swap | A function still returns the same shape, but a default changed from `false` to `true` | It "improved" something adjacent to the task |
Import rewrite | A module now resolves to a different implementation with the same name | Autocomplete-style path guessing, common in monorepos |
The second one is the expensive one, because the type checker is happy and only a behavioural test catches it. If your suite did not catch it, that is the test to write before you move on. We go deeper on the reading part in how to debug AI generated code.
Clean up the throwaway history
Once you have the fix, collapse the wip commits back into something reviewable:
git reset --soft 0c4d8aa
git commit -m "feat(billing): retry failed invoice charges
Adds bounded retry with backoff for card declines.
Fixes null default in resolveCustomer that broke invoice.test.ts."Nobody needs your bisect scaffolding in the permanent history, and a reviewer needs the seam between the feature and the incidental fix. Writing good commit messages with AI covers the format side of this.
Stop producing unbisectable commits
The whole exercise above exists because the agent handed you one commit. That is a settable behaviour, not a law of nature.
Put the rule in your agent instructions file, not in a chat message. Something like: commit after each logically complete unit, never batch unrelated changes, never touch files outside the stated scope without saying so. Chat instructions decay across a long session, project files do not.
Ask for a plan before execution and check the file list in it. If the plan for a billing change includes edits under `src/auth/`, that is the moment to ask why, not after the build breaks. Reviewing an agent plan before it runs is the cheapest intervention available.
Constrain the working set. Agents that can only see the files they need cannot casually refactor the ones they cannot see. This is the same discipline as stopping an agent from editing unrelated files.
Run the build between phases, not only at the end. A forty minute unsupervised run produces a forty minute regression window. Five eight-minute runs with a build check between them produce five eight-minute windows, and you rarely need bisect at all.
The honest tradeoff: more commits means more review surface and a noisier log. In exchange, the next regression takes four minutes to locate instead of forty. On any codebase you plan to keep, that trade pays.
When bisect is the wrong tool
Skip all of this if the failure is not a regression. Bisect assumes a clean transition from good to bad somewhere in a range. It cannot help you when the test was already flaky, when the break came from a dependency update fetched at install time rather than a code change, or when the failure depends on state outside the repository such as a migrated database or a rotated key. If your reproduction command fails intermittently on a commit you know is green, stop bisecting and fix the flake first.
FAQ
Can I bisect without splitting the agent's commit?
You can run it, but on a one-commit range it will just name that commit. Splitting is what turns bisect from a formality into an answer. The soft reset takes about ninety seconds and is fully reversible.
What if the intermediate commits do not compile?
Exit 125 from your bisect script when the build fails. Git treats that as skip and tries an adjacent revision instead of recording a false result.
How long should the reproduction command take?
Aim for under twenty seconds. Bisect runs it about log2(N) times, so on a 32-commit range that is five runs. Narrow to the specific failing test file rather than running the whole suite.
Does this work when the agent rewrote history itself?
Only if the commits still exist locally. Check `git reflog` first. If the agent amended or force-pushed over the range, recover the lost commits from the reflog before starting, because bisect needs a good revision to anchor against.
Is there a way to avoid this entirely?
Mostly, yes. Commit granularity rules in your agent instructions file, a plan review before execution, and a build check between phases remove most of the cases where you would need bisect at all.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


