<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Debugging on Londopy</title><link>https://londopy.github.io/blog/tags/debugging/</link><description>Recent content in Debugging on Londopy</description><image><title>Londopy</title><url>https://londopy.github.io/blog/og-image.png</url><link>https://londopy.github.io/blog/og-image.png</link></image><generator>Hugo</generator><language>en-us</language><copyright>2026 Londopy · Posts CC BY 4.0 · Code MIT</copyright><lastBuildDate>Thu, 24 Sep 2026 01:52:31 -0700</lastBuildDate><atom:link href="https://londopy.github.io/blog/tags/debugging/index.xml" rel="self" type="application/rss+xml"/><item><title>git bisect run: Let Git Find the Bug for You</title><link>https://londopy.github.io/blog/posts/git-bisect-run/</link><pubDate>Thu, 08 Oct 2026 09:00:00 -0700</pubDate><guid>https://londopy.github.io/blog/posts/git-bisect-run/</guid><description>Something broke, and you don&amp;#39;t know which of the last 400 commits did it. Git can binary-search your history and find the exact commit, automatically.</description><content:encoded><![CDATA[<p>It worked last month. It doesn&rsquo;t work now. There are 400 commits in between.</p>
<p>You could read them all. Or you could let Git find the guilty one in about nine steps.</p>
<h2 id="the-idea-binary-search-through-history">The idea: binary search through history</h2>
<p><code>git bisect</code> takes two points: a commit where things were <strong>good</strong> and one where they&rsquo;re <strong>bad</strong>. It checks out the commit halfway between and asks you which it is. Then it throws away the half that can&rsquo;t contain the bug and repeats.</p>
<p>Each step halves the search. 400 commits takes at most 9 steps. 10,000 commits takes at most 14.</p>
<h2 id="manual-bisect">Manual bisect</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect start
</span></span><span class="line"><span class="cl">git bisect bad                 <span class="c1"># the current commit is broken</span>
</span></span><span class="line"><span class="cl">git bisect good v2.3.0         <span class="c1"># this tag was fine</span>
</span></span></code></pre></div><p>Git checks out a commit in the middle and tells you roughly how many steps are left. Test it, then tell Git:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect good    <span class="c1"># this one works</span>
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">git bisect bad     <span class="c1"># this one is broken</span>
</span></span></code></pre></div><p>Repeat until Git prints:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">a1b2c3d is the first bad commit
</span></span></code></pre></div><p>When you&rsquo;re done, go back to where you started:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect reset
</span></span></code></pre></div><p>You can also give both endpoints up front:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect start HEAD v2.3.0
</span></span></code></pre></div><h2 id="the-real-magic-git-bisect-run">The real magic: <code>git bisect run</code></h2>
<p>If you can write a script that tells good from bad, Git will do the whole search without you:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect start HEAD v2.3.0
</span></span><span class="line"><span class="cl">git bisect run ./check.sh
</span></span></code></pre></div><p>Git checks out each candidate commit, runs your script, and reads its <strong>exit code</strong>:</p>
<table>
	<thead>
			<tr>
					<th>Exit code</th>
					<th>Meaning</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>good</td>
			</tr>
			<tr>
					<td>1 to 127 (not 125)</td>
					<td>bad</td>
			</tr>
			<tr>
					<td>125</td>
					<td>skip: this commit can&rsquo;t be tested</td>
			</tr>
			<tr>
					<td>128 or higher</td>
					<td>abort the whole bisect</td>
			</tr>
	</tbody>
</table>
<p>Your test suite is often the script already:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect run pytest tests/test_login.py
</span></span><span class="line"><span class="cl">git bisect run cargo <span class="nb">test</span> parse_header
</span></span><span class="line"><span class="cl">git bisect run npm <span class="nb">test</span>
</span></span></code></pre></div><h2 id="writing-a-good-check-script">Writing a good check script</h2>
<p>The script should test <em>only</em> the bug you&rsquo;re hunting. A full test suite may fail for unrelated reasons partway through history.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="c1"># check.sh: exit 0 if the bug is absent, 1 if present, 125 if we can&#39;t tell</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># If it doesn&#39;t build, we can&#39;t test this commit. Skip it.</span>
</span></span><span class="line"><span class="cl">make &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">||</span> <span class="nb">exit</span> <span class="m">125</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The actual check</span>
</span></span><span class="line"><span class="cl">./app --version <span class="p">|</span> grep -q <span class="s2">&#34;expected output&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">0</span> <span class="o">||</span> <span class="nb">exit</span> <span class="m">1</span>
</span></span></code></pre></div><p>A few tips:</p>
<ul>
<li><strong>Use 125 for &ldquo;can&rsquo;t test.&rdquo;</strong> Commits that don&rsquo;t compile, or that predate the feature entirely, should be skipped, not marked bad. Otherwise Git blames the wrong commit.</li>
<li><strong>Keep the script outside the repo,</strong> or untracked. Bisect checks out old commits, and an old commit might not have your script, or might have an older version of it.</li>
<li><strong>Make it fast.</strong> It runs once per step.</li>
<li><strong>Make it deterministic.</strong> A flaky test sends the search in the wrong direction and gives a confident, wrong answer.</li>
</ul>
<h2 id="useful-extras">Useful extras</h2>
<p><strong>Skip a commit by hand:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect skip
</span></span></code></pre></div><p><strong>See or replay the session.</strong> If you mark a commit wrong, save the log, edit out the mistake, and replay it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect log &gt; bisect.log
</span></span><span class="line"><span class="cl"><span class="c1"># edit bisect.log</span>
</span></span><span class="line"><span class="cl">git bisect reset
</span></span><span class="line"><span class="cl">git bisect replay bisect.log
</span></span></code></pre></div><p><strong>Visualize what&rsquo;s left:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect visualize --oneline
</span></span></code></pre></div><p><strong>Hunt for things that aren&rsquo;t bugs.</strong> &ldquo;Good&rdquo; and &ldquo;bad&rdquo; are just labels. You can rename them:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect start --term-old<span class="o">=</span>fast --term-new<span class="o">=</span>slow
</span></span><span class="line"><span class="cl">git bisect fast v2.3.0
</span></span><span class="line"><span class="cl">git bisect slow HEAD
</span></span></code></pre></div><p>Now you can find the commit where something got slow, or the commit where a feature first appeared.</p>
<p><strong>Ignore merged-in side branches.</strong> On a repo with many merges, <code>--first-parent</code> only follows the main line of history:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git bisect start --first-parent
</span></span></code></pre></div><h2 id="things-you-should-know">Things you should know</h2>
<ul>
<li><strong>Bisect needs a clean working tree.</strong> Commit or stash your changes first.</li>
<li><strong>It finds the first bad commit, not the cause.</strong> Sometimes the &ldquo;first bad commit&rdquo; just exposed a bug that was already there. Read the diff before blaming anyone.</li>
<li><strong>Small commits make bisect better.</strong> If the guilty commit changed 3,000 lines, knowing which commit it was doesn&rsquo;t help much. Another reason to commit in small, focused steps.</li>
</ul>
<h2 id="closing-thought">Closing thought</h2>
<p>The next time you catch yourself scrolling through <code>git log</code> trying to guess when something broke, stop. Write a ten-line script, run <code>git bisect run</code>, and go get a coffee.</p>
]]></content:encoded></item></channel></rss>