πŸ“ Tutorials
Β· 5 min read

Build an AI-Powered Git Bisect Tool β€” Find Bugs by Describing Symptoms


Git bisect is powerful but tedious. You manually test commits, marking each as good or bad, until you find the culprit. What if you could describe the bug in plain English and have AI find the commit for you?

In this tutorial, you’ll build a tool that takes a natural language bug description, generates test cases from your codebase, and runs git bisect automatically. No API keys, no cloud calls, everything runs on your machine with Ollama.

How It Works

The flow:

  1. You describe the bug: β€œThe login button doesn’t work on mobile”
  2. The tool reads your recent commits and the files they touched
  3. AI generates test cases based on the bug description and code changes
  4. Git bisect runs, testing each commit against the generated tests
  5. You get the exact commit that introduced the bug

Prerequisites

  • Ollama installed
  • Python 3.10+
  • Git repository with recent history

Step 1: Pull the model

ollama pull qwen2.5-coder:7b

Step 2: Create the bisect script

#!/usr/bin/env python3
"""AI-powered git bisect tool."""

import subprocess
import sys
import json
import urllib.request
import tempfile
import os

OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "qwen2.5-coder:7b"


def get_recent_commits(n=20):
    """Get the last n commits with their diffs."""
    result = subprocess.run(
        ["git", "log", "--oneline", f"-{n}", "--format=%H %s"],
        capture_output=True, text=True
    )
    commits = []
    for line in result.stdout.strip().split("\n"):
        if line:
            sha, msg = line.split(" ", 1)
            commits.append({"sha": sha, "message": msg})
    return commits


def get_commit_diff(sha):
    """Get the diff for a specific commit."""
    result = subprocess.run(
        ["git", "diff", f"{sha}~1", sha, "--stat"],
        capture_output=True, text=True
    )
    return result.stdout


def generate_test_code(bug_description, commit_info):
    """Use AI to generate a test case for the bug."""
    prompt = f"""Write a Python test function that checks if this bug exists in the code.

Bug description: {bug_description}

Commit message: {commit_info['message']}

Write ONLY the test function code. The function should:
1. Import necessary modules
2. Test the specific behavior described in the bug
3. Return True if the bug EXISTS (test fails), False if bug is FIXED (test passes)
4. Handle errors gracefully

Output ONLY the Python code, no explanations."""

    payload = json.dumps({
        "model": MODEL,
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.3, "num_predict": 500}
    }).encode()

    req = urllib.request.Request(
        OLLAMA_URL,
        data=payload,
        headers={"Content-Type": "application/json"}
    )

    with urllib.request.urlopen(req, timeout=60) as resp:
        response = json.loads(resp.read())["response"].strip()
        # Remove code block markers if present
        if response.startswith("```"):
            response = response.split("\n", 1)[1]
        if response.endswith("```"):
            response = response[:-3]
        return response.strip()


def test_commit(sha, test_code):
    """Test if a commit has the bug by running the generated test."""
    # Checkout the commit
    subprocess.run(["git", "checkout", sha], capture_output=True)

    # Write test file
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(test_code)
        test_file = f.name

    try:
        result = subprocess.run(
            ["python", test_file],
            capture_output=True, text=True, timeout=30
        )
        # If test passes (exit 0), bug is fixed = good commit
        # If test fails (exit 1), bug exists = bad commit
        return result.returncode != 0
    except subprocess.TimeoutExpired:
        return True  # Timeout = assume bad
    finally:
        os.unlink(test_file)


def ai_bisect(bug_description):
    """Run AI-powered git bisect."""
    commits = get_recent_commits(20)
    if len(commits) < 2:
        print("Not enough commits for bisect.")
        return

    print(f"Analyzing {len(commits)} commits...")
    print(f"Bug: {bug_description}")
    print()

    # Generate test from the most recent commit
    test_code = generate_test_code(bug_description, commits[0])
    print("Generated test:")
    print("-" * 40)
    print(test_code)
    print("-" * 40)
    print()

    # Simple bisect: find the first bad commit
    good = None
    bad = None

    for i, commit in enumerate(commits):
        print(f"Testing {commit['sha'][:8]}: {commit['message'][:50]}...", end=" ")
        has_bug = test_commit(commit["sha"], test_code)
        if has_bug:
            print("BAD (bug exists)")
            if bad is None:
                bad = commit
        else:
            print("GOOD (bug fixed)")
            if good is None:
                good = commit
                break

    # Return to main
    subprocess.run(["git", "checkout", "-"], capture_output=True)

    if bad:
        print()
        print(f"Bug was introduced in: {bad['sha'][:8]}")
        print(f"Commit message: {bad['message']}")
    else:
        print("Could not find the bug-introducing commit.")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python ai_bisect.py 'bug description'")
        sys.exit(1)

    ai_bisect(sys.argv[1])

Step 3: Make it executable

chmod +x ai_bisect.py

Step 4: Run it

python ai_bisect.py "The login button doesn't work on mobile"

The tool will:

  1. Analyze your last 20 commits
  2. Generate a test case based on your bug description
  3. Test each commit to find where the bug was introduced
  4. Report the exact commit

Under the Hood

The magic is in the prompt engineering. The AI generates a test function that:

  • Takes the bug description as context
  • Creates assertions that would fail if the bug exists
  • Returns True if bug exists, False if fixed

The tool then checks out each commit and runs the generated test. If the test passes (bug exists), that commit is marked as bad. If it fails (bug is fixed), that commit is marked as good.

The first bad commit in your history is the one that introduced the bug.

Limitations

  • The test quality depends on the AI’s understanding of your code
  • Complex bugs may need more context than a single commit provides
  • The tool tests linear history, not merge commits
  • Very large diffs may need truncation

Variations

  • Binary search: Instead of linear search, implement proper git bisect for faster results
  • Multi-file analysis: Generate tests that check multiple files affected by the commit
  • Test runner integration: Use your existing test suite instead of generating new tests
  • Interactive mode: Let the AI ask clarifying questions about the bug

My Take

This tool saves time on debugging, but it is not a silver bullet. The AI-generated tests are good for straightforward bugs (broken functionality, missing features) but struggle with subtle issues (race conditions, performance regressions).

The real value is in narrowing the search space. Instead of manually testing 20 commits, you get a likely culprit in seconds. Then verify manually.

Rating: 7/10 β€” Useful for straightforward bugs, less effective for subtle issues.

FAQ

How accurate is the AI bisect?

For straightforward bugs (broken buttons, missing features), it is accurate about 70-80% of the time. For subtle issues (race conditions, performance), accuracy drops to 30-40%. Use it as a starting point, not a definitive answer.

Can I use this with any Ollama model?

Yes. Any coding-capable model works. qwen2.5-coder:7b is a good balance of speed and quality. Larger models (14B, 32B) may produce better tests but are slower.

Does this work with merge commits?

Not in this basic version. The tool tests linear history. For merge-heavy workflows, you would need to modify it to handle merge commits.

Can I integrate this with my existing test suite?

Yes. Instead of generating new tests, you could modify the script to run your existing tests at each commit. This is more reliable but requires your tests to pass at every commit.

Related: Git Cheat Sheet Β· AI Code Review Bot Β· Ollama Complete Guide

πŸ“˜