Skip to content

What is git bisect?

Git bisect finds the commit that introduced a bug by binary search, testing commits between a known good one and a known bad one to halve the range.

Last updated , 9 min read

What is git bisect?

Git bisect is a Git command that runs a binary search through a project's commit history to find the first commit where a behavior changed. A developer marks one commit where the software worked and one where it fails. Git then checks out commits between them, and each result halves the range until one commit is left.

Git's manual says git bisect uses "a binary search algorithm to find which commit in your project's history introduced a bug." It suits a regression, a behavior that worked in an earlier version and broke later.

Bisection is one of the narrowing techniques of debugging. It needs a check that gives a clear good or bad answer on any commit, usually built from the bug's reproduction steps.

How does git bisect work?

A bisect session moves through these steps:

  1. A developer runs git bisect start and marks a failing commit, usually the current one, with git bisect bad.
  2. The developer marks a working commit, e.g. the last release, with git bisect good.
  3. Git checks out a commit about halfway between the two and prints how many commits are left to test.
  4. The developer tests that commit and marks it good or bad.
  5. Git drops the half of the range that the mark rules out and checks out the middle of the rest.
  6. When one commit is left, Git prints it as the first bad commit and points the reference refs/bisect/bad at it.

A manual session uses these commands:

git bisect start
git bisect bad              # current commit fails
git bisect good v2.4.0      # last known good
# test the checked-out commit, then mark it
git bisect good             # or: git bisect bad
git bisect reset            # when done
How bisect halves the range 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 good first bad bad Test 1 checks commit 8. It is good, so the change is later. Test 2 checks commit 12. It is bad, so the change is at 12 or earlier. Test 3 checks commit 10. It is good. Test 4 checks commit 11. It is bad. Commit 11 is the first bad commit, found in 4 tests.
Each line is the range still in doubt, and each dot is the commit tested. Each test rules out about half of the range.

Each test halves the range, so 100 commits take about 7 tests.

The terms old and new can replace good and bad, e.g. to find the commit that fixed a bug. To undo a wrong mark, a developer saves the output of git bisect log to a file, deletes the wrong line, and runs git bisect replay on that file.

What is an example of git bisect?

Here is an illustrative example. Acme Co. sells furniture online. In its release v2.4.0, customers could edit their delivery address during checkout and keep their cart.

Since then, a coding agent has added 40 commits to the main branch. A customer reports "The address saved, but the cart emptied." A developer reproduces the failure and starts the search:

  1. The developer runs git bisect start, git bisect bad on the current commit, and git bisect good v2.4.0.
  2. Git checks out commit 20 of the 40 and prints Bisecting: 19 revisions left to test after this (roughly 4 steps).
  3. The developer adds 2 items to the cart, starts checkout, and changes the address. The cart keeps 2 items, so the developer runs git bisect good.
  4. Git checks out commit 30, where the cart empties, so the developer runs git bisect bad.
  5. Three more tests, on commits 25, 27, and 26, come out good, bad, and good.

After the fifth test, Git prints the first bad commit, shortened here:

2e01d6a5367c4893bfd8a57a8560d498a06dc457 is the first bad commit
commit 2e01d6a5367c4893bfd8a57a8560d498a06dc457
Author: Acme developer <dev@example.com>

    Keep checkout state in one record

 src/checkout/address.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

The message of commit 27 describes a refactor, but its diff shows the address handler replacing the whole checkout record with one that holds only the address. This example is simplified. A real project would also need each old commit to build with the dependencies it had then.

How do you automate it with git bisect run?

The command git bisect run replaces the person in the loop with a script. Git runs the script on each commit it checks out and marks the commit from the script's exit code:

  • Exit code 0. The commit is good.
  • Exit code 125. The commit cannot be tested, so Git skips it.
  • Exit codes 1 to 127, other than 125. The commit is bad.
  • Any other code. Git stops the whole bisect, e.g. on 255 from a C program's exit(-1).

A crash can end with a code above 128, e.g. 139 after a segmentation fault, so the script below turns that code into 1. A commit that does not build would fail the test too, so the script skips it.

For the Acme bug, the check is a Playwright test written from the reproduction steps. Older commits do not contain that test, so the script copies it in on each run. Git's manual advises keeping bisect scripts outside the repository:

#!/bin/sh
# ~/cart-check.sh, kept outside the repository
npm ci --silent || exit 125          # no clean install: skip this commit
npm run build --silent || exit 125   # does not build: skip this commit
cp ~/address-keeps-cart.spec.ts tests/ || exit 125
npx playwright test tests/address-keeps-cart.spec.ts --retries=0
status=$?
[ "$status" -gt 127 ] && status=1    # a crash counts as bad
rm tests/address-keeps-cart.spec.ts
exit $status

The --retries=0 flag stops a retry from turning a failure into a pass. Before the search, the developer runs the script once on v2.4.0, where it exits 0, and once on the current commit, where it exits 1. Then git bisect start HEAD v2.4.0 and git bisect run sh ~/cart-check.sh run the search, and git bisect reset ends it.

Git tests the same five commits as the manual search and prints bisect found first bad commit. The reset clears the bisect state and returns to the commit checked out before git bisect start. It also ends a search early, because Git has no git bisect stop command.

What changes when a coding agent writes the code?

A coding agent can add many commits in one session, but a longer range adds few tests. The bigger change is in what each commit holds. An agent can leave a series of commits that never built. The git bisect skip command takes a range, so one command skips the series.

A coding agent can run git bisect run itself, because the command needs only a shell and an exit code. Bisect checks out old commits in the working tree where it runs. A separate working tree, e.g. one made with git worktree add, keeps the search away from unfinished work in the main checkout.

When an agent's change breaks working features, the check should come from the bug report, not from the agent's reading of the code. A check written from the code can test a different behavior, and the search then names the commit that changed that behavior. A practical adjustment is a pre-commit Git hook that runs the build. Then later commits are more likely to build, though --no-verify can skip the hook.

What makes bisecting hard?

Bisect gives a wrong or unclear answer under these conditions:

  • A check that is not repeatable. A flaky test can fail on a good commit or pass on a bad one, and the search continues in the wrong half with no warning. For an intermittent bug, the script can run the test several times with retries off and exit 1 if any run fails.
  • Commits that cannot be tested. A commit that does not build gets git bisect skip, and Git moves to a nearby commit. If a skipped commit sits next to the first bad commit, Git lists several candidates instead of one.
  • Large commits. Bisect cannot split a commit, so a commit that changed 30 files leaves a long diff to read. Review has the same problem with pull request size.
  • More than one change. Bisect relies on a single switch from good to bad in the range. If the behavior broke, was fixed, and broke again, the search can name the second break instead of the first.
  • Merged branches. A merged branch can hold commits that were broken or did not build, although the merge itself was fine. The --first-parent option follows only the first parent of each merge, so the search names the merge commit.

How is git bisect different from reading the git log?

Reading the log means scanning commit messages and diffs with git log for a likely cause. The git blame command shows the commit that last changed each line. The Pro Git book says that annotating a file this way "helps if you know where the issue is to begin with."

Git bisect needs no message or suspect file, because it tests behavior at each step. Bisect names the commit, and its diff is where root cause analysis starts.

FAQs

What does git bisect skip do?

Git bisect skip marks the current commit as one that cannot be tested, and Git moves to a nearby commit. The command also takes a range, e.g. a series of commits that never built. A skip next to the first bad commit leaves several candidates instead of one.

Which exit code makes git bisect run skip a commit?

Exit code 125 makes git bisect run skip a commit, because Git reads it as a commit that cannot be tested. Code 0 marks a commit good, codes 1 to 127 other than 125 mark it bad, and any other code stops the bisect.

Can git bisect find a flaky failure?

Git bisect can find a flaky failure when the script runs the test several times with retries off and exits 1 if any run fails. A single run can pass on a bad commit and send the search into the wrong half.

How do you end a git bisect session?

A git bisect session ends with git bisect reset, which clears the bisect state and returns to the commit checked out before the session started. Git has no git bisect stop command, so the reset also ends a search early.