I like git bisect a lot. Something is broken, nobody knows since when, and bisect finds the commit in a few steps: you give it a good commit, a bad one, and a script that tells them apart. But when the search is over, it doesn’t clean up. You stay on a detached HEAD, often not even on the culprit. git status keeps saying that you are bisecting, and your branch is locked until you remember to run git bisect reset. I forget it almost every time.

Git 2.56 adds the missing last step:

git bisect start --reset-when-found main v1
git bisect run ./test.sh

As soon as the first bad commit is found, Git reports it and ends the session, and you are back on the branch you started from. With --reset-when-found=found, it leaves you right on the culprit instead, also without a session.

In this article, I show how it works step by step: a small project, a bug hidden somewhere in ten releases, and the hunt for it, first the old way and then with the new option. Every step has the commands and the real output, so you can repeat it in your own terminal.

This is my third article about Git 2.56. In the first one, about git add --resolved, I showed how I installed Git 2.56 with mise without compiling anything. The second one is about git branch --delete-merged.

How it works

git bisect start [--reset-when-found[=<where>]] <bad> <good>
git bisect run [--reset-when-found[=<where>]] <cmd>

<where> is original or found:

  • original (the default, if you write just --reset-when-found) goes back to the branch or commit that was checked out before git bisect start;
  • found leaves the first bad commit checked out.

In both cases the bisect session is over. You can give the option to git bisect start or to git bisect run, and if you give it to both, the one on run wins. It also works when you answer good and bad yourself. And it only does something when a culprit is found: if the bisection fails, the session stays open as before.

Step by step

The commit IDs in your terminal will be different from mine, and that’s normal.

Step 1: A shop and its config

A small shop with one config file. The line to watch is vat = 20.

demo $ mkdir shop
demo $ cd shop
shop $ git init
Initialized empty Git repository in …/shop/.git/

A new directory and an empty repository.

shop (main) $ vim shop.conf
  1 [shop]
  2 version = 1
  3 timeout = 30
  4 vat = 20
"shop.conf" [New] 4L, 41B written
shop (main) $ git add shop.conf
shop (main) $ git commit -m "Release 1: the shop opens"
[main (root-commit) 67d0bd7] Release 1: the shop opens
 1 file changed, 4 insertions(+)
 create mode 100644 shop.conf
shop (main) $ git tag v1

The config is the first release. The tag v1 will be our known good commit later.

Step 2: A few weeks of releases later

Nine more releases follow, one tag each. Somewhere in there is a bug, and ten commits are already too many to check by hand. If you follow along, this small shell function makes the same releases:

release() {
  printf '[shop]\nversion = %s\ntimeout = %s\nvat = %s\n' "$1" "$2" "$3" > shop.conf
  git commit -qam "Release $1: $4" && git tag "v$1"
}
release 2 30 20 "gift cards"
release 3 30 20 "faster search"
release 4 30 20 "dark mode"
release 5 30 20 "new logo"
release 6 30 20 "PayPal checkout"
release 7 10 2 "quicker checkout"
release 8 10 2 "wishlist"
release 9 10 2 "order history"
release 10 10 2 "mobile layout"
shop (main) $ git log --oneline
e001716 (HEAD -> main, tag: v10) Release 10: mobile layout
c18e6a3 (tag: v9) Release 9: order history
b502bdc (tag: v8) Release 8: wishlist
4b6d267 (tag: v7) Release 7: quicker checkout
8a37775 (tag: v6) Release 6: PayPal checkout
33dc10e (tag: v5) Release 5: new logo
d914813 (tag: v4) Release 4: dark mode
856b592 (tag: v3) Release 3: faster search
264b07d (tag: v2) Release 2: gift cards
67d0bd7 (tag: v1) Release 1: the shop opens

Ten releases, from v1 to v10.

Step 3: A bug report: prices are shown without VAT

shop (main) $ git show v1:shop.conf | grep vat
vat = 20
shop (main) $ grep vat shop.conf
vat = 2

Customers say that VAT is missing. Release 1 was fine, but on main the VAT is 2 instead of 20.

shop (main) $ vim ../vat-test.sh
  1 #!/bin/sh
  2 # exit 0 = good, 1 = bad
  3 grep -qx "vat = 20" shop.conf
"../vat-test.sh" [New] 3L, 65B written
shop (main) $ chmod +x ../vat-test.sh
shop (main) $ ../vat-test.sh

A test for it. It lives outside the repository (../vat-test.sh), so every commit bisect checks out is tested by the same script. Exit code 0 means good, 1 means bad. On main it prints nothing, but exits with 1: bad.

Step 4: You were in the middle of something else

Bugs never come at a good moment.

shop (main) $ git switch -c topic
Switched to a new branch 'topic'
shop (topic) $ vim banner.txt
  1 Summer sale: -20% on everything!
"banner.txt" [New] 1L, 33B written
shop (topic) $ git add banner.txt
shop (topic) $ git commit -m "Add a summer sale banner"
[topic abdcca9] Add a summer sale banner
 1 file changed, 1 insertion(+)
 create mode 100644 banner.txt

My own work lives on a branch, topic, with a commit of its own.

shop (topic) $ git log --oneline --graph -3
* abdcca9 (HEAD -> topic) Add a summer sale banner
* e001716 (tag: v10, main) Release 10: mobile layout
* c18e6a3 (tag: v9) Release 9: order history
shop (topic) $ git status
On branch topic
nothing to commit, working tree clean

That’s where I want to be when the hunt is over.

Step 5: The old way

shop (topic) $ git bisect start main v1
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo

main is bad and v1 is good. The prompt changes right away: a detached commit and |BISECTING.

shop (33dc10e...|BISECTING) $ git bisect run ../vat-test.sh
running '../vat-test.sh'
Bisecting: 2 revisions left to test after this (roughly 1 step)
[4b6d267fc27bb4c90f0664668111f990ec5f1570] Release 7: quicker checkout
running '../vat-test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[8a37775e8df629177a970e555be139525b473a9c] Release 6: PayPal checkout
running '../vat-test.sh'
4b6d267fc27bb4c90f0664668111f990ec5f1570 is the first 'bad' commit
commit 4b6d267fc27bb4c90f0664668111f990ec5f1570
Author: Demo User <demo@example.com>
Date:   Thu Jan 1 10:11:00 2026 +0000

    Release 7: quicker checkout

 shop.conf | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
bisect found first 'bad' commit

git bisect run runs the test on three commits and names the culprit: 4b6d267, Release 7.

Step 6: …and you are stuck in the session

shop (8a37775...|BISECTING) $ git status
HEAD detached at 8a37775
You are currently bisecting, started from branch 'topic'.
  (use "git bisect reset" to get back to the original branch)

nothing to commit, working tree clean

The search is over, but the session isn’t. HEAD is on 8a37775, Release 6. It’s not even the culprit, just the last commit the test ran on. And git status still says You are currently bisecting.

shop (8a37775...|BISECTING) $ git branch -d topic 2>&1 | sed "s|$PWD|.|"
error: cannot delete branch 'topic' used by worktree at '.' for bisect

My branch is locked too. Git 2.56 at least says why: for bisect at the end of the message is new, Git 2.55 didn’t say it. The sed only makes the long path shorter.

shop (8a37775...|BISECTING) $ git bisect reset
Previous HEAD position was 8a37775 Release 6: PayPal checkout
Switched to branch 'topic'

Only git bisect reset brings me back to topic.

Step 7: --reset-when-found: back where you started

shop (topic) $ git bisect start --reset-when-found main v1
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo
shop (33dc10e...|BISECTING) $ git bisect run ../vat-test.sh
running '../vat-test.sh'
Bisecting: 2 revisions left to test after this (roughly 1 step)
[4b6d267fc27bb4c90f0664668111f990ec5f1570] Release 7: quicker checkout
running '../vat-test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[8a37775e8df629177a970e555be139525b473a9c] Release 6: PayPal checkout
running '../vat-test.sh'
4b6d267fc27bb4c90f0664668111f990ec5f1570 is the first 'bad' commit
commit 4b6d267fc27bb4c90f0664668111f990ec5f1570
Author: Demo User <demo@example.com>
Date:   Thu Jan 1 10:11:00 2026 +0000

    Release 7: quicker checkout

 shop.conf | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
bisect found first 'bad' commit

The same search, with one extra option on start. The report is the same.

shop (topic) $ git status
On branch topic
nothing to commit, working tree clean

But look at the prompt: shop (topic) $. Git ran the reset for me, and there is no session anymore.

Step 8: Manual sessions too

Not every bug has a test script. The option works the same way when I answer good and bad myself.

shop (topic) $ git bisect start --reset-when-found main v1
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo

The same start as before.

shop (33dc10e...|BISECTING) $ grep vat shop.conf
vat = 20
shop (33dc10e...|BISECTING) $ git bisect good
Bisecting: 2 revisions left to test after this (roughly 1 step)
[4b6d267fc27bb4c90f0664668111f990ec5f1570] Release 7: quicker checkout
shop (4b6d267...|BISECTING) $ grep vat shop.conf
vat = 2
shop (4b6d267...|BISECTING) $ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[8a37775e8df629177a970e555be139525b473a9c] Release 6: PayPal checkout
shop (8a37775...|BISECTING) $ grep vat shop.conf
vat = 20
shop (8a37775...|BISECTING) $ git bisect good
4b6d267fc27bb4c90f0664668111f990ec5f1570 is the first 'bad' commit
commit 4b6d267fc27bb4c90f0664668111f990ec5f1570 (tag: v7)
Author: Demo User <demo@example.com>
Date:   Thu Jan 1 10:11:00 2026 +0000

    Release 7: quicker checkout

 shop.conf | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

On every commit Git checks out, I look at the file and answer: vat = 20 is good, vat = 2 is bad. The answer that finds the culprit also ends the session, and I’m back on topic. You can see it in the prompt in the next step.

Step 9: --reset-when-found=found: land on the culprit

shop (topic) $ git bisect start main v1
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo
shop (33dc10e...|BISECTING) $ git bisect run --reset-when-found=found ../vat-test.sh
running '../vat-test.sh'
Bisecting: 2 revisions left to test after this (roughly 1 step)
[4b6d267fc27bb4c90f0664668111f990ec5f1570] Release 7: quicker checkout
running '../vat-test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[8a37775e8df629177a970e555be139525b473a9c] Release 6: PayPal checkout
running '../vat-test.sh'
4b6d267fc27bb4c90f0664668111f990ec5f1570 is the first 'bad' commit
commit 4b6d267fc27bb4c90f0664668111f990ec5f1570
Author: Demo User <demo@example.com>
Date:   Thu Jan 1 10:11:00 2026 +0000

    Release 7: quicker checkout

 shop.conf | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
bisect found first 'bad' commit

This time the option goes to git bisect run, and with =found Git leaves the culprit checked out.

shop (4b6d267...) $ git status
HEAD detached at 4b6d267
nothing to commit, working tree clean

The prompt shows 4b6d267... without |BISECTING. I’m on the culprit, and there is no session to clean up.

Step 10: Look at the culprit

shop (4b6d267...) $ git show
commit 4b6d267fc27bb4c90f0664668111f990ec5f1570 (HEAD, tag: v7)
Author: Demo User <demo@example.com>
Date:   Thu Jan 1 10:11:00 2026 +0000

    Release 7: quicker checkout

diff --git a/shop.conf b/shop.conf
index 98450d6..5a6b7a3 100644
--- a/shop.conf
+++ b/shop.conf
@@ -1,4 +1,4 @@
 [shop]
-version = 6
-timeout = 30
-vat = 20
+version = 7
+timeout = 10
+vat = 2

I’m already on it, so git show is enough. The new timeout was intended, but the VAT change was a slip of the finger.

Step 11: Fix it and check it with the same test

shop (4b6d267...) $ git switch -c fix-vat main
Previous HEAD position was 4b6d267 Release 7: quicker checkout
Switched to a new branch 'fix-vat'
shop (fix-vat) $ vim shop.conf
  1 [shop]
  2 version = 10
  3 timeout = 10
  4 vat = 20
"shop.conf" 4L, 42B written

The fix goes on a new branch from main.

shop (fix-vat) $ git diff
diff --git a/shop.conf b/shop.conf
index 4552938..044996c 100644
--- a/shop.conf
+++ b/shop.conf
@@ -1,4 +1,4 @@
 [shop]
 version = 10
 timeout = 10
-vat = 2
+vat = 20
shop (fix-vat) $ ../vat-test.sh && echo "VAT test: good"
VAT test: good

One line changed, and the same test that found the bug now says good.

shop (fix-vat) $ git commit -am "Restore VAT to 20% (broken in release 7)"
[fix-vat 09d6f25] Restore VAT to 20% (broken in release 7)
 1 file changed, 1 insertion(+), 1 deletion(-)
shop (fix-vat) $ git log --oneline --graph -3
* 09d6f25 (HEAD -> fix-vat) Restore VAT to 20% (broken in release 7)
* e001716 (tag: v10, main) Release 10: mobile layout
* c18e6a3 (tag: v9) Release 9: order history

The commit message says where the bug came from.

Step 12: Not together with --no-checkout

shop (fix-vat) $ git bisect start --no-checkout --reset-when-found main v1
error: options '--reset-when-found' and '--no-checkout' cannot be used together

--no-checkout bisects without touching the working tree, so there is nothing to reset. Git rejects the combination, and no session is started.

Step 13: No culprit, no reset

shop (fix-vat) $ git bisect start --reset-when-found main v1
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo
shop (33dc10e...|BISECTING) $ git bisect run ../vat-tset.sh
running '../vat-tset.sh'
'../vat-tset.sh': line 1: ../vat-tset.sh: No such file or directory
[67d0bd7e61cb4a7cb789a632196da03a9dabd6b3] Release 1: the shop opens
running '../vat-tset.sh'
'../vat-tset.sh': line 1: ../vat-tset.sh: No such file or directory
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo
error: bogus exit code 127 for 'good' revision

A typo in the script name: vat-tset.sh. The shell returns exit code 127, Git checks the script on the good commit, gets 127 again, and gives up. No culprit was found, so --reset-when-found does nothing, and the prompt still says |BISECTING. It’s the same when the script skips every commit (exit code 125).

shop (33dc10e...|BISECTING) $ git bisect reset
Previous HEAD position was 33dc10e Release 5: new logo
Switched to branch 'fix-vat'

In this case, I clean up as usual.

Step 14: Make it a habit

shop (fix-vat) $ git config set --global alias.hunt 'bisect start --reset-when-found'

An alias makes the safe way the short way.

shop (fix-vat) $ git hunt main v1
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[33dc10ee4f87a2540687302e007fce2d7769ac40] Release 5: new logo
shop (33dc10e...|BISECTING) $ git bisect run ../vat-test.sh | tail -n 1
bisect found first 'bad' commit

git hunt starts a session that ends by itself. (| tail -n 1 only keeps the last line of the report.) After the run, I’m back on fix-vat.

What to remember

  • --reset-when-found (or =original) brings you back to where you started. --reset-when-found=found leaves the first bad commit checked out. In both cases there is no session left.
  • It works with git bisect start and with git bisect run. If you give it to both, the one on run wins.
  • It works for manual good and bad answers too.
  • It only does something when a culprit is found. If the bisection fails, you still need git bisect reset.
  • It can’t be used with --no-checkout.
  • Keep the test script outside the repository, so every commit is tested by the same script.

Conclusion

In summary, --reset-when-found is a small option, but it fixes the part of git bisect that I always forget. The search ends where it should: on my branch, or right on the culprit, if I want to look at it. And with the hunt alias, the safe way is also the shortest one.

Links: