|$ curl https://forge-ai.dev/api/markdown?path=docs/git/bisect
$cat docs/git-bisect.md
updated Recently·20 min read·published

Git Bisect

GitIntermediate🎯Free Tools
Introduction

git bisect uses binary search through your commit history to find the exact commit that introduced a bug. Instead of manually checking commits one by one (O(n)), bisect checks them logarithmically (O(log n)) — finding the culprit in ~10 steps for 1000 commits.

Manual Bisect

Manual bisect requires you to test each commit and tell Git whether it's good or bad. Git then narrows down the range until it finds the first bad commit.

bisect.sh
Bash
1# Start bisect
2git bisect start
3
4# Mark current commit as bad (has the bug)
5git bisect bad
6
7# Mark a known good commit (before the bug)
8git bisect good abc1234
9
10# Git checks out a commit in the middle
11# Test your code, then mark it:
12git bisect good # if this commit works
13# OR
14git bisect bad # if this commit has the bug
15
16# Repeat until Git finds the culprit
17# Bisect will output:
18# abc1234 is the first bad commit
19# commit abc1234
20# Author: Someone
21# Date: Mon Jul 7 14:30:00 2026
22
23# When done, return to main
24git bisect reset
25
26# One-liner: bisect between good and bad commits
27git bisect start HEAD abc1234
28git bisect run npm test

info

Always mark git bisect good or git bisect bad accurately. One wrong mark can send the bisect down the wrong path. Test carefully at each step.
Automated Bisect (bisect run)

git bisect runautomates the entire process by running a script at each commit. The script's exit code determines good (0) or bad (non-zero). No manual intervention needed.

automated-bisect.sh
Bash
1# Automated bisect with a test script
2git bisect start HEAD v1.0.0
3git bisect run npm test
4
5# The script must exit:
6# 0 = good (test passes)
7# 1-124 = bad (test fails)
8# 125 = skip (can't test this commit)
9# 126-127 = abort bisect
10
11# Custom test script with multiple checks
12cat > bisect-test.sh << 'EOF'
13#!/bin/bash
14set -e
15
16# Try to build
17npm run build 2>/dev/null || exit 1
18
19# Run the specific test
20npm test -- --grep "user authentication" || exit 1
21
22# Check if a specific function exists
23if ! grep -q "validateInput" src/utils.ts; then
24 exit 1 # Bug: missing function
25fi
26
27exit 0
28EOF
29
30chmod +x bisect-test.sh
31git bisect start HEAD v1.0.0
32git bisect run ./bisect-test.sh
33
34# Skip untestable commits (e.g., can't compile)
35git bisect run sh -c "npm install && npm test" || git bisect skip

best practice

Make your bisect test script fast and deterministic. A 10-step bisect with a 30-second test script completes in 5 minutes. A 5-minute test script takes 50 minutes. Optimize the test before running bisect.
Real-World Examples
real-bisect.sh
Bash
1# Find which commit broke a specific API endpoint
2git bisect start HEAD v2.0.0
3git bisect run curl -sf http://localhost:3000/api/health || exit 1
4
5# Find which commit introduced a performance regression
6cat > perf-test.sh << 'EOF'
7#!/bin/bash
8npm run build
9node server.js &
10SERVER_PID=$!
11sleep 2
12
13# Measure response time
14TIME=$(curl -w "%{time_total}" -s http://localhost:3000/api/data)
15kill $SERVER_PID
16
17# Fail if response time > 500ms
18if (( $(echo "$TIME > 0.5" | bc -l) )); then
19 echo "Slow response: ${TIME}s"
20 exit 1
21fi
22exit 0
23EOF
24
25git bisect start HEAD v1.5.0
26git bisect run ./perf-test.sh
27
28# Find which commit broke TypeScript types
29git bisect run sh -c "npm install && npx tsc --noEmit"
30
31# Find which commit broke a snapshot test
32git bisect run npx jest --updateSnapshot
Visual Regression Bisect
visual-bisect.sh
Bash
1# Bisect visual changes using screenshots
2cat > visual-test.sh << 'EOF'
3#!/bin/bash
4npm run build && npm start &
5APP_PID=$!
6sleep 3
7
8# Take screenshot with Playwright
9npx playwright screenshot http://localhost:3000 current.png
10
11# Compare with reference using ImageMagick
12if compare -metric AE reference.png current.png diff.png 2>/dev/null; then
13 kill $APP_PID
14 exit 0 # Good — no visual difference
15else
16 kill $APP_PID
17 exit 1 # Bad — visual regression
18fi
19EOF
20
21git bisect start HEAD v1.0.0
22git bisect run ./visual-test.sh

info

For visual regression bisect, commit reference screenshots to the repo. This gives bisect a stable baseline to compare against at each step.
Advanced Patterns

Extra depth for production teams — conflict strategies, automation, and recovery.

Automation-friendly flags

automation.sh
Bash
1git status --porcelain=v1
2git diff --name-only --diff-filter=ACMR
3git log -1 --pretty=format:%H
4git merge-base HEAD origin/main
5git rev-list --count origin/main..HEAD

Recovery drill

recovery.sh
Bash
1git reflog | head -20
2git fsck --lost-found | head
3git branch rescue HEAD@{1}
4git log --oneline rescue -5
🔥

pro tip

Practice recovery in /tmp labs before you need it on a deadline.
Production Checklist
  • No secrets in history for this change set
  • CI green on the PR
  • Rebased or merged with latest main
  • Rollback plan: revert SHA known
  • Tags/releases updated if needed
prod-check.sh
Bash
1git status -sb
2git log --oneline origin/main..HEAD
3git diff --check
4git rev-parse HEAD
Additional Examples
more-a.sh
Bash
1# Cherry-pick a range onto a release branch
2git switch release/1.2
3git cherry-pick abc123^..def456
4# conflict?
5git status
6# fix, then:
7git add -A && git cherry-pick --continue
8# or abort:
9# git cherry-pick --abort
more-b.sh
Bash
1# Bisect with a script
2git bisect start
3git bisect bad HEAD
4git bisect good v1.0.0
5git bisect run ./scripts/test-bug.sh
6git bisect reset
more-c.sh
Bash
1# Submodule bump
2git submodule update --remote --merge libs/shared
3git add libs/shared
4git commit -m "chore(deps): bump shared submodule"
5git submodule status
more-d.sh
Bash
1# Workflow: trunk-based short PR
2git fetch origin
3git switch -c fix/timeout origin/main
4# change + test
5git commit -am "fix: request timeout"
6git push -u origin HEAD
7gh pr create --fill
8gh pr checks
9gh pr merge --squash --delete-branch
Automated Bisect Deep Dive

Bisect is binary search over history. Mark good/bad commits; Git checks out midpoints until it finds the first bad commit.

Manual session

bi-manual.sh
Bash
1git bisect start
2git bisect bad HEAD
3git bisect good v1.2.0
4# test current checkout
5npm test -- --runInBand
6git bisect good # or bad / skip
7# when done:
8git bisect reset

Scripted bisect run

bi-run.sh
Bash
1#!/usr/bin/env bash
2# scripts/bisect-test.sh — exit 0 good, 1 bad, 125 skip
3npm ci --silent
4npm test -- --runInBand tests/regressions/bug.spec.ts
5
bi-run-invoke.sh
Bash
1chmod +x scripts/bisect-test.sh
2git bisect start HEAD v1.2.0
3git bisect run ./scripts/bisect-test.sh
4git bisect reset
5# Visualize:
6git bisect log > /tmp/bisect.log
7git bisect visualize

Skip untestable commits

bi-skip.sh
Bash
1# Build broken at midpoint
2git bisect skip
3# Or skip a range
4git bisect skip abc111..def222

danger

Make the test script deterministic and fast. Flaky tests make bisect lie.
bi-lab.sh
Bash
1rm -rf /tmp/bi && mkdir /tmp/bi && cd /tmp/bi
2git init -b main
3for i in 1 2 3 4 5 6 7 8; do echo $i > f; git add f; git commit -m c$i; done
4echo BUG > f; git commit -am bug
5# good is c3, bad is HEAD
6git bisect start HEAD HEAD~5
7# simulate: if f contains BUG -> bad
8
Agent Notes — Bisect
  • Always bisect reset when finished
  • Prefer git bisect run with a script over many manual steps
  • Exit 125 to skip when build cannot run
  • Do not bisect on a dirty worktree — stash or worktree first
$Blueprint — Engineering Documentation·Section ID: GIT-BS-01·Revision: 1.0

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.