|$ curl https://forge-ai.dev/api/markdown?path=docs/git/bisect
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 |
| 2 | git bisect start |
| 3 | |
| 4 | # Mark current commit as bad (has the bug) |
| 5 | git bisect bad |
| 6 | |
| 7 | # Mark a known good commit (before the bug) |
| 8 | git bisect good abc1234 |
| 9 | |
| 10 | # Git checks out a commit in the middle |
| 11 | # Test your code, then mark it: |
| 12 | git bisect good # if this commit works |
| 13 | # OR |
| 14 | git 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 |
| 24 | git bisect reset |
| 25 | |
| 26 | # One-liner: bisect between good and bad commits |
| 27 | git bisect start HEAD abc1234 |
| 28 | git 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 |
| 2 | git bisect start HEAD v1.0.0 |
| 3 | git 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 |
| 12 | cat > bisect-test.sh << 'EOF' |
| 13 | #!/bin/bash |
| 14 | set -e |
| 15 | |
| 16 | # Try to build |
| 17 | npm run build 2>/dev/null || exit 1 |
| 18 | |
| 19 | # Run the specific test |
| 20 | npm test -- --grep "user authentication" || exit 1 |
| 21 | |
| 22 | # Check if a specific function exists |
| 23 | if ! grep -q "validateInput" src/utils.ts; then |
| 24 | exit 1 # Bug: missing function |
| 25 | fi |
| 26 | |
| 27 | exit 0 |
| 28 | EOF |
| 29 | |
| 30 | chmod +x bisect-test.sh |
| 31 | git bisect start HEAD v1.0.0 |
| 32 | git bisect run ./bisect-test.sh |
| 33 | |
| 34 | # Skip untestable commits (e.g., can't compile) |
| 35 | git 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 |
| 2 | git bisect start HEAD v2.0.0 |
| 3 | git bisect run curl -sf http://localhost:3000/api/health || exit 1 |
| 4 | |
| 5 | # Find which commit introduced a performance regression |
| 6 | cat > perf-test.sh << 'EOF' |
| 7 | #!/bin/bash |
| 8 | npm run build |
| 9 | node server.js & |
| 10 | SERVER_PID=$! |
| 11 | sleep 2 |
| 12 | |
| 13 | # Measure response time |
| 14 | TIME=$(curl -w "%{time_total}" -s http://localhost:3000/api/data) |
| 15 | kill $SERVER_PID |
| 16 | |
| 17 | # Fail if response time > 500ms |
| 18 | if (( $(echo "$TIME > 0.5" | bc -l) )); then |
| 19 | echo "Slow response: ${TIME}s" |
| 20 | exit 1 |
| 21 | fi |
| 22 | exit 0 |
| 23 | EOF |
| 24 | |
| 25 | git bisect start HEAD v1.5.0 |
| 26 | git bisect run ./perf-test.sh |
| 27 | |
| 28 | # Find which commit broke TypeScript types |
| 29 | git bisect run sh -c "npm install && npx tsc --noEmit" |
| 30 | |
| 31 | # Find which commit broke a snapshot test |
| 32 | git bisect run npx jest --updateSnapshot |
Visual Regression Bisect
visual-bisect.sh
Bash
| 1 | # Bisect visual changes using screenshots |
| 2 | cat > visual-test.sh << 'EOF' |
| 3 | #!/bin/bash |
| 4 | npm run build && npm start & |
| 5 | APP_PID=$! |
| 6 | sleep 3 |
| 7 | |
| 8 | # Take screenshot with Playwright |
| 9 | npx playwright screenshot http://localhost:3000 current.png |
| 10 | |
| 11 | # Compare with reference using ImageMagick |
| 12 | if compare -metric AE reference.png current.png diff.png 2>/dev/null; then |
| 13 | kill $APP_PID |
| 14 | exit 0 # Good — no visual difference |
| 15 | else |
| 16 | kill $APP_PID |
| 17 | exit 1 # Bad — visual regression |
| 18 | fi |
| 19 | EOF |
| 20 | |
| 21 | git bisect start HEAD v1.0.0 |
| 22 | git 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.
$Blueprint — Engineering Documentation·Section ID: GIT-BS-01·Revision: 1.0