Your AI-assisted feature branch has several commits, and the quote total is suddenly wrong. Asking the assistant to rewrite the calculator may hide the cause. First turn the business failure into a repeatable check, then use Git bisect to locate the change where that check starts failing.
This guide runs an actual disposable Git experiment. Four authored revisions simulate an evolving app; no AI coding session or real customer repository is involved. The result identifies the deliberately introduced defect, then returns the temporary repository to its original branch and HEAD.
A tiny business rule with a visible regression
Fictional scenario: Orbit Workshop’s sample quote rule is unit price multiplied by item count, with no fee. A unit price of 1,000 sample cents and a count of two should total 2,000 cents. Revision three accidentally adds a 100-cent fee. These are invented fixtures, not My Biz Heroes prices, customer results or Jason Ead’s personal experience.
| Authored revision | Formula | Expected classification |
|---|---|---|
| 1 | price × count | Good |
| 2 | count × price | Good |
| 3 | price × count + 100 | First bad |
| 4 | 100 + price × count | Bad |
One clean good-to-bad transition makes this a suitable teaching example. A flaky service, changing model response or intermittent timing bug needs a more dependable predicate before automated bisection is useful.
What Git is doing
Bisect narrows a history between known good and bad revisions using test results. Its run command treats exit zero as good and ordinary nonzero statuses as bad, with special handling for skip and abort statuses. Reset ends the session and returns to the starting checkout by default. Official Git bisect manual.
The drill checks both endpoints first. Its Python unittest exits zero on the approved revision and one on the changed revision. After bisection it compares Git’s bad reference with the known injected commit. A nonzero exit by itself is insufficient: a broken interpreter or missing dependency is not evidence of the business regression.
Run the complete isolated drill
Prerequisites are Python and Git available on PATH. This run used Windows, Python 3.14.3 and Git 2.51.0.windows.1. The current online manual describes a newer Git release; this example uses only commands actually exercised on the recorded version.
Save the following as bisect_drill.py in a disposable folder. It creates its own temporary repository, sets fixture identity only in that repository, disables commit signing and hooks there, removes inherited GIT_ overrides from child-process configuration and never contacts a remote. It does not modify your global Git configuration or open an existing repository.
"""Create and bisect a disposable authored history; no existing repo opened."""
from pathlib import Path
import json
import os
import subprocess
import sys
import tempfile
TEST = '''import unittest
from quote import total
class QuoteTest(unittest.TestCase):
def test_total(self):
for count in (1, 2, 5):
with self.subTest(count=count):
self.assertEqual(total(1000, count), 1000 * count)
if __name__ == '__main__':
unittest.main()
'''
def drill():
log = []
# Do not inherit a caller's alternate repository/index configuration.
env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
env['PYTHONDONTWRITEBYTECODE'] = '1'
# Fixed fixture timestamps make commit IDs repeatable for this drill.
env['GIT_AUTHOR_DATE'] = env['GIT_COMMITTER_DATE'] = '2026-10-17T09:00:00-0400'
with tempfile.TemporaryDirectory(prefix='orbit-bisect-') as folder:
repo = Path(folder)
def run(args, check=True):
r = subprocess.run(args, cwd=repo, env=env, text=True,
capture_output=True)
log.append({'command': args, 'returncode': r.returncode,
'stdout': r.stdout, 'stderr': r.stderr})
if check and r.returncode:
raise RuntimeError(r.stderr or r.stdout)
return r
def git(*args): return run(['git', *args]).stdout.strip()
git('-c', 'init.templateDir=', 'init', '-b', 'main')
hooks = repo / 'empty-hooks'
hooks.mkdir()
for key, value in [('user.name', 'Fictional Drill'),
('user.email', 'drill@example.invalid'),
('commit.gpgsign', 'false'),
('core.hooksPath', str(hooks)),
('core.autocrlf', 'false')]:
git('config', '--local', key, value)
(repo / 'test_quote.py').write_text(TEST, encoding='utf-8')
commits = []
formulas = ['price * count', 'count * price',
'price * count + 100', '100 + price * count']
for i, formula in enumerate(formulas):
(repo / 'quote.py').write_text(
'def total(price, count):\n return ' + formula + '\n',
encoding='utf-8')
git('add', 'quote.py', 'test_quote.py')
git('commit', '-m', f'fixture revision {i + 1}')
commits.append(git('rev-parse', 'HEAD'))
test = [sys.executable, '-X', 'utf8', '-m', 'unittest', 'test_quote.py']
bad_exit = run(test, check=False).returncode
if bad_exit != 1: raise AssertionError('bad endpoint not test failure 1')
git('checkout', commits[0])
run(test)
git('checkout', 'main')
start = git('rev-parse', 'HEAD')
try:
git('bisect', 'start', commits[-1], commits[0])
git('bisect', 'run', *test)
found = git('rev-parse', 'refs/bisect/bad')
bisect_log = git('bisect', 'log')
if found != commits[2]: raise AssertionError('wrong first bad commit')
finally:
git('bisect', 'reset')
if git('rev-parse', 'HEAD') != start: raise AssertionError('HEAD changed')
if git('branch', '--show-current') != 'main': raise AssertionError('branch changed')
if git('status', '--porcelain'): raise AssertionError('worktree dirty')
return {'git_version': git('--version'), 'python_version': sys.version.split()[0],
'first_bad_revision': 3, 'identified_expected_commit': True,
'good_endpoint_exit': 0, 'bad_endpoint_exit': bad_exit,
'main_restored': True, 'worktree_clean': True,
'bisect_log': bisect_log, 'commands': log}
if __name__ == '__main__':
print(json.dumps(drill(), indent=2))
Run:
python -X utf8 bisect_drill.py
The program prints a full JSON command transcript, including expected failing unittest output. Those failed tests are the signal being investigated. An error in the drill itself stops execution instead of printing a success report. The summary fields from the recorded run were:
{
"git_version": "git version 2.51.0.windows.1",
"python_version": "3.14.3",
"first_bad_revision": 3,
"identified_expected_commit": true,
"good_endpoint_exit": 0,
"bad_endpoint_exit": 1,
"main_restored": true,
"worktree_clean": true
}
The complete program and command were extracted from the final article into another disposable directory and rerun. The good and bad endpoints classified correctly, Git found the injected revision, and reset restored the starting main branch and HEAD with a clean worktree. Commit IDs and local command paths can vary with environment; the checked outcome is the injected revision, not a hard-coded hash in the article.
Use the finding before asking for a fix
In a real investigation, preserve your current work first and use an isolated checkout with no production credentials. Historical tests and build scripts execute code, so inspect them before running a bisect over an unfamiliar repository. Keep a stable regression test available across the candidate revisions; a test that only exists at the newest commit cannot classify older commits without a deliberate test harness.
Give your coding assistant the failing business case, the identified diff and the observed test output. Ask for a minimal explanation and repair, then run the failing check plus the relevant broader suite. Finding a change is not proof of author intent, model blame or a complete root cause. Dependencies, data and deployment configuration may also have changed.
For separating an AI task from unfinished work, see giving a coding task its own Git worktree. That guide isolates changes; this drill localizes a regression in an authored history.
When the search should stop
The manual assigns exit 125 to an untestable revision and reserves other statuses for abort conditions; unavailable commands commonly produce 126 or 127, which fall inside the ordinary bad-status range. Skipping revisions can leave more than one possible culprit. Check the documented contract and inspect your harness before interpreting the answer. Do not turn an infrastructure failure into a bad-commit label.
This drill exercises ordinary good/bad results only. It does not test skip ambiguity, an aborting harness, merges, changing dependencies, network failures, a nondeterministic model or a production checkout. It has no command timeout and is intended for a tiny local fixture; interruption can prevent its normal finally-based cleanup. It is not a general-purpose automated repository runner.
The three counts verify this narrow multiplication rule, not all quote behavior or currency handling. No measured developer time saving, AI comparison, real incident or production fix is claimed. The cover is generated conceptual detective artwork; the interior table describes the executed fixture history.
Jason Ead publishes AlgoThoughts AI News and connects practical engineering with My Biz Heroes: websites, automation, workflows, AI tools and training. Explore Jason’s work. The useful business result is a smaller, evidence-backed repair target instead of another speculative rewrite.
