<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jeanbza.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jeanbza.github.io/" rel="alternate" type="text/html" /><updated>2026-08-20T04:12:19+00:00</updated><id>https://jeanbza.github.io/feed.xml</id><title type="html">Jean Barkhuysen</title><subtitle>SWE at Netflix working on distributed media processing/storage. Former Googler working on distributed storage. Former surname de Klerk.
This is my personal website. The views represented here are my own, and do not represent my employer.</subtitle><entry><title type="html">A generic heap in Go</title><link href="https://jeanbza.github.io/go/heap/2026/08/19/go-generic-heap.html" rel="alternate" type="text/html" title="A generic heap in Go" /><published>2026-08-19T08:55:23+00:00</published><updated>2026-08-19T08:55:23+00:00</updated><id>https://jeanbza.github.io/go/heap/2026/08/19/go-generic-heap</id><content type="html" xml:base="https://jeanbza.github.io/go/heap/2026/08/19/go-generic-heap.html"><![CDATA[<p>Let’s re-imagine the venerable <a href="https://pkg.go.dev/container/heap">container/heap</a>
package with generics instead of an interface.</p>

<h3 id="usage-without-generic-methods">Usage without generic methods</h3>

<p><a href="https://go.dev/play/p/3-QCvtyZVH1">Here’s how a user uses heap today:</a></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"container/heap"</span>
	<span class="s">"fmt"</span>
<span class="p">)</span>

<span class="k">type</span> <span class="n">IntHeap</span> <span class="p">[]</span><span class="kt">int</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="n">IntHeap</span><span class="p">)</span> <span class="n">Len</span><span class="p">()</span> <span class="kt">int</span>           <span class="p">{</span> <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">h</span><span class="p">)</span> <span class="p">}</span>
<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="n">IntHeap</span><span class="p">)</span> <span class="n">Less</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> <span class="k">return</span> <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">h</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="p">}</span>
<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="n">IntHeap</span><span class="p">)</span> <span class="n">Swap</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="kt">int</span><span class="p">)</span>      <span class="p">{</span> <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">h</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">h</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">}</span>
<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">IntHeap</span><span class="p">)</span> <span class="n">Push</span><span class="p">(</span><span class="n">x</span> <span class="n">any</span><span class="p">)</span>        <span class="p">{</span> <span class="o">*</span><span class="n">h</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">,</span> <span class="n">x</span><span class="o">.</span><span class="p">(</span><span class="kt">int</span><span class="p">))</span> <span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">IntHeap</span><span class="p">)</span> <span class="n">Pop</span><span class="p">()</span> <span class="n">any</span> <span class="p">{</span>
	<span class="n">old</span> <span class="o">:=</span> <span class="o">*</span><span class="n">h</span>
	<span class="n">n</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="n">old</span><span class="p">)</span>
	<span class="n">x</span> <span class="o">:=</span> <span class="n">old</span><span class="p">[</span><span class="n">n</span><span class="o">-</span><span class="m">1</span><span class="p">]</span>
	<span class="o">*</span><span class="n">h</span> <span class="o">=</span> <span class="n">old</span><span class="p">[</span><span class="m">0</span> <span class="o">:</span> <span class="n">n</span><span class="o">-</span><span class="m">1</span><span class="p">]</span>
	<span class="k">return</span> <span class="n">x</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">h</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">IntHeap</span><span class="p">{</span><span class="m">2</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">5</span><span class="p">}</span>
	<span class="n">heap</span><span class="o">.</span><span class="n">Init</span><span class="p">(</span><span class="n">h</span><span class="p">)</span>
	<span class="n">heap</span><span class="o">.</span><span class="n">Push</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="m">3</span><span class="p">)</span>
	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"minimum: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)[</span><span class="m">0</span><span class="p">])</span>
	<span class="k">for</span> <span class="n">h</span><span class="o">.</span><span class="n">Len</span><span class="p">()</span> <span class="o">&gt;</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"%d "</span><span class="p">,</span> <span class="n">heap</span><span class="o">.</span><span class="n">Pop</span><span class="p">(</span><span class="n">h</span><span class="p">))</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And, in case you missed it, <code class="language-plaintext highlighter-rouge">Push</code>, <code class="language-plaintext highlighter-rouge">Pop</code>, and so on use <code class="language-plaintext highlighter-rouge">any</code>. So, you could
write <code class="language-plaintext highlighter-rouge">heap.Push(h, 3)</code> and then <code class="language-plaintext highlighter-rouge">heap.Push(h, "foo")</code> and it’d compile! But
you’d get a panic at runtime:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go build .
# aww
$ go run .
panic: interface conversion: interface {} is string, not int
</code></pre></div></div>

<p>Overall, it’s definitely usable and great not to have to re-implement heap
bubbling, but the UX is a bit clunky. It’s not great to lose types, and it’s not
great to have to implement 5 functions every time I have a different type of
heap.</p>

<h3 id="usage-with-generic-methods">Usage with generic methods</h3>

<p>With generic methods, a differently-defined heap (which we’ll look at shortly)
<a href="https://go.dev/play/p/GiRznCugGt-">could just be:</a></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"container/heap"</span>
	<span class="s">"fmt"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">h</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">heap</span><span class="o">.</span><span class="n">Heap</span><span class="p">[</span><span class="kt">int</span><span class="p">]{</span><span class="m">2</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">5</span><span class="p">}</span>
	<span class="n">h</span><span class="o">.</span><span class="n">Init</span><span class="p">()</span>
	<span class="n">h</span><span class="o">.</span><span class="n">Push</span><span class="p">(</span><span class="m">3</span><span class="p">)</span>

	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"minimum: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)[</span><span class="m">0</span><span class="p">])</span>
	<span class="k">for</span> <span class="n">h</span><span class="o">.</span><span class="n">Len</span><span class="p">()</span> <span class="o">&gt;</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"%d "</span><span class="p">,</span> <span class="n">h</span><span class="o">.</span><span class="n">Pop</span><span class="p">())</span>
	<span class="p">}</span>
	<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So much easier! No more having to define 5 methods per type: we just declare a
heap[<type>] and use it.</type></p>

<p>And, we’re using generics instead of <code class="language-plaintext highlighter-rouge">any</code>, so we have types on our methods. If
we try to do <code class="language-plaintext highlighter-rouge">h.Push("foo")</code>, we get a nice compiler error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go build .
# example.com/foo
./main.go:20:9: cannot use "foo" (untyped string constant) as int value in argument to h.Push
# yay!
</code></pre></div></div>

<h3 id="implementing-heap-with-generic-methods">Implementing heap with generic methods</h3>

<p>And finally, here’s how the heap package looks with generic methods. Mostly the
same as it is, except we define Push and Pop directly on a heap type rather than
as free functions that take an interface. And, cmp aleady gives us <code class="language-plaintext highlighter-rouge">cmp.Ordered</code>
as the type constraint, so we can just use that directly. The bubbling logic
remains the same:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="s">"cmp"</span>

<span class="k">type</span> <span class="n">Heap</span><span class="p">[</span><span class="n">T</span> <span class="n">cmp</span><span class="o">.</span><span class="n">Ordered</span><span class="p">]</span> <span class="p">[]</span><span class="n">T</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Len</span><span class="p">()</span> <span class="kt">int</span>           <span class="p">{</span> <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">h</span><span class="p">)</span> <span class="p">}</span>
<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Less</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> <span class="k">return</span> <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">h</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="p">}</span>
<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Swap</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="kt">int</span><span class="p">)</span>      <span class="p">{</span> <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">h</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">h</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Init</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">n</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)</span>
	<span class="k">for</span> <span class="n">i</span> <span class="o">:=</span> <span class="n">n</span><span class="o">/</span><span class="m">2</span> <span class="o">-</span> <span class="m">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span><span class="o">--</span> <span class="p">{</span>
		<span class="n">h</span><span class="o">.</span><span class="n">down</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Push</span><span class="p">(</span><span class="n">x</span> <span class="n">T</span><span class="p">)</span> <span class="p">{</span>
	<span class="o">*</span><span class="n">h</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
	<span class="n">h</span><span class="o">.</span><span class="n">up</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)</span> <span class="o">-</span> <span class="m">1</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Pop</span><span class="p">()</span> <span class="n">T</span> <span class="p">{</span>
	<span class="n">n</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)</span> <span class="o">-</span> <span class="m">1</span>
	<span class="n">h</span><span class="o">.</span><span class="n">Swap</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
	<span class="n">h</span><span class="o">.</span><span class="n">down</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
	<span class="n">x</span> <span class="o">:=</span> <span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)[</span><span class="n">n</span><span class="p">]</span>
	<span class="o">*</span><span class="n">h</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)[</span><span class="o">:</span><span class="n">n</span><span class="p">]</span>
	<span class="k">return</span> <span class="n">x</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Remove</span><span class="p">(</span><span class="n">i</span> <span class="kt">int</span><span class="p">)</span> <span class="n">T</span> <span class="p">{</span>
	<span class="n">n</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)</span> <span class="o">-</span> <span class="m">1</span>
	<span class="k">if</span> <span class="n">n</span> <span class="o">!=</span> <span class="n">i</span> <span class="p">{</span>
		<span class="n">h</span><span class="o">.</span><span class="n">Swap</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">h</span><span class="o">.</span><span class="n">down</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">h</span><span class="o">.</span><span class="n">up</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="n">x</span> <span class="o">:=</span> <span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)[</span><span class="n">n</span><span class="p">]</span>
	<span class="o">*</span><span class="n">h</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">)[</span><span class="o">:</span><span class="n">n</span><span class="p">]</span>
	<span class="k">return</span> <span class="n">x</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">Fix</span><span class="p">(</span><span class="n">i</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="o">!</span><span class="n">h</span><span class="o">.</span><span class="n">down</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="o">*</span><span class="n">h</span><span class="p">))</span> <span class="p">{</span>
		<span class="n">h</span><span class="o">.</span><span class="n">up</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">up</span><span class="p">(</span><span class="n">j</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">for</span> <span class="p">{</span>
		<span class="n">i</span> <span class="o">:=</span> <span class="p">(</span><span class="n">j</span> <span class="o">-</span> <span class="m">1</span><span class="p">)</span> <span class="o">/</span> <span class="m">2</span>
		<span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="n">j</span> <span class="o">||</span> <span class="o">!</span><span class="n">h</span><span class="o">.</span><span class="n">Less</span><span class="p">(</span><span class="n">j</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">break</span>
		<span class="p">}</span>
		<span class="n">h</span><span class="o">.</span><span class="n">Swap</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>
		<span class="n">j</span> <span class="o">=</span> <span class="n">i</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">Heap</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="n">down</span><span class="p">(</span><span class="n">i0</span><span class="p">,</span> <span class="n">n</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
	<span class="n">i</span> <span class="o">:=</span> <span class="n">i0</span>
	<span class="k">for</span> <span class="p">{</span>
		<span class="n">j1</span> <span class="o">:=</span> <span class="m">2</span><span class="o">*</span><span class="n">i</span> <span class="o">+</span> <span class="m">1</span>
		<span class="k">if</span> <span class="n">j1</span> <span class="o">&gt;=</span> <span class="n">n</span> <span class="o">||</span> <span class="n">j1</span> <span class="o">&lt;</span> <span class="m">0</span> <span class="p">{</span>
			<span class="k">break</span>
		<span class="p">}</span>
		<span class="n">j</span> <span class="o">:=</span> <span class="n">j1</span>
		<span class="k">if</span> <span class="n">j2</span> <span class="o">:=</span> <span class="n">j1</span> <span class="o">+</span> <span class="m">1</span><span class="p">;</span> <span class="n">j2</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="o">&amp;&amp;</span> <span class="n">h</span><span class="o">.</span><span class="n">Less</span><span class="p">(</span><span class="n">j2</span><span class="p">,</span> <span class="n">j1</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">j</span> <span class="o">=</span> <span class="n">j2</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">h</span><span class="o">.</span><span class="n">Less</span><span class="p">(</span><span class="n">j</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">break</span>
		<span class="p">}</span>
		<span class="n">h</span><span class="o">.</span><span class="n">Swap</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>
		<span class="n">i</span> <span class="o">=</span> <span class="n">j</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="n">i0</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="go" /><category term="heap" /><summary type="html"><![CDATA[An example of the heap package, re-imagined as a generic heap.]]></summary></entry><entry><title type="html">Graceful shutdown with HTTP and gRPC in Go</title><link href="https://jeanbza.github.io/go/kubernetes/grpc/2026/08/18/graceful-shutdown.html" rel="alternate" type="text/html" title="Graceful shutdown with HTTP and gRPC in Go" /><published>2026-08-18T08:55:23+00:00</published><updated>2026-08-18T08:55:23+00:00</updated><id>https://jeanbza.github.io/go/kubernetes/grpc/2026/08/18/graceful-shutdown</id><content type="html" xml:base="https://jeanbza.github.io/go/kubernetes/grpc/2026/08/18/graceful-shutdown.html"><![CDATA[<p>At Netflix, we run our containers on Kubernetes. Sometimes Kubernetes has to
terminate your container, for any number of reasons: a new version is being
deployed, or your container is being migrated elsewhere, or a scale down is
happening, etc. As the <a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/">Kubernetes Pod Lifecycle docs</a>
describes, the application in your container will be sent a SIGTERM/SIGINT, and
then Kubernetes will wait for your application to gracefully shut down, and if
it hasn’t done so after a small grace period, Kubernetes will SIGKILL it.</p>

<p>So, I often find myself starting applications by writing graceful shutdown
logic. And since most of the application programming that I do uses HTTP and
gRPC servers, I end up writing graceful shutdown for those fairly frequently.</p>

<p>I thought I’d codify that here for myself, and for anyone else interested in
seeing how to gracefully shut down your gRPC and HTTP servers in Go.</p>

<p>The key parts are:</p>

<ol>
  <li>We use <code class="language-plaintext highlighter-rouge">signal.NotifyContext</code> to attach SIGINT/SIGTERM to context
cancellation.</li>
  <li>We then feed that context to an <code class="language-plaintext highlighter-rouge">errgroup.WithContext</code> to create context
cancellation aware goroutines.</li>
  <li>We use that errgroup to spawn goroutines to serve HTTP/gRPC.</li>
  <li>We use that errgroup to spawn HTTP/gRPC goroutines that watch for context
cancellation and begin graceful shutdown.
    <ul>
      <li>Apart from the cancellation provided by <code class="language-plaintext highlighter-rouge">signal.NotifyContext</code>, any other
 reason for cancellation also triggers graceful shutdown. So this is a nice
 generic way to wire up your application for shutdown due to any reason you
 might care to express.</li>
    </ul>
  </li>
  <li>We bound graceful shutdown to a timeout, after which we forcefully shut down.
    <ul>
      <li>This part is take it or leave it: Kubernetes will send SIGKILL after
 whatever grace period it allows. So, you could just use that mechanism. I
 personally prefer to own shutdown as much as I can, though, so that I can
 make sure to call all the logs/metrics/tracing flushing.</li>
    </ul>
  </li>
</ol>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"context"</span>
	<span class="s">"errors"</span>
	<span class="s">"flag"</span>
	<span class="s">"fmt"</span>
	<span class="s">"log/slog"</span>
	<span class="s">"net"</span>
	<span class="s">"net/http"</span>
	<span class="s">"os"</span>
	<span class="s">"os/signal"</span>
	<span class="s">"syscall"</span>
	<span class="s">"time"</span>

	<span class="s">"golang.org/x/sync/errgroup"</span>
	<span class="s">"google.golang.org/grpc"</span>

	<span class="n">pb</span> <span class="s">"google.golang.org/grpc/examples/helloworld/helloworld"</span>
<span class="p">)</span>

<span class="k">var</span> <span class="n">httpPort</span> <span class="o">=</span> <span class="n">flag</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"httpPort"</span><span class="p">,</span> <span class="m">7000</span><span class="p">,</span> <span class="s">"The port to serve HTTP on"</span><span class="p">)</span>
<span class="k">var</span> <span class="n">grpcPort</span> <span class="o">=</span> <span class="n">flag</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"grpcPort"</span><span class="p">,</span> <span class="m">8000</span><span class="p">,</span> <span class="s">"The port to server gRPC on"</span><span class="p">)</span>

<span class="k">const</span> <span class="n">httpShutdownTimeout</span> <span class="o">=</span> <span class="m">5</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span> <span class="c">// How long HTTP shutdown is allowed to take.</span>
<span class="k">const</span> <span class="n">grpcShutdownTimeout</span> <span class="o">=</span> <span class="m">5</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span> <span class="c">// How long gRPC shutdown is allowed to take.</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">flag</span><span class="o">.</span><span class="n">Parse</span><span class="p">()</span>

	<span class="c">// SIGINT/SIGTERM signals to our application will result in the ctx being cancelled.</span>
	<span class="n">ctx</span><span class="p">,</span> <span class="n">stop</span> <span class="o">:=</span> <span class="n">signal</span><span class="o">.</span><span class="n">NotifyContext</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">(),</span> <span class="n">syscall</span><span class="o">.</span><span class="n">SIGINT</span><span class="p">,</span> <span class="n">syscall</span><span class="o">.</span><span class="n">SIGTERM</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">stop</span><span class="p">()</span>

	<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">run</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"application exited with 1"</span><span class="p">,</span> <span class="s">"error"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
		<span class="c">// Make sure to flush logs/metrics/traces/etc by this point! And keep in</span>
		<span class="c">// mind that defer doesn't run when you os.Exit(1), so you might need</span>
		<span class="c">// to invoke them manually depending on where you performed init.</span>
		<span class="n">os</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"application exited with 0"</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">run</span><span class="p">(</span><span class="n">ctx</span> <span class="n">context</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
	<span class="c">// Start with port binding.</span>
	<span class="n">httpListener</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">Listen</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">":%d"</span><span class="p">,</span> <span class="o">*</span><span class="n">httpPort</span><span class="p">))</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"http listen on port %d failed: %v"</span><span class="p">,</span> <span class="o">*</span><span class="n">httpPort</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">httpListener</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">grpcServerListener</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">Listen</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">":%d"</span><span class="p">,</span> <span class="o">*</span><span class="n">grpcPort</span><span class="p">))</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"grpc listen on port %d failed: %v"</span><span class="p">,</span> <span class="o">*</span><span class="n">grpcPort</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">grpcServerListener</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">eg</span><span class="p">,</span> <span class="n">gCtx</span> <span class="o">:=</span> <span class="n">errgroup</span><span class="o">.</span><span class="n">WithContext</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>

	<span class="c">// HTTP server.</span>
	<span class="n">mux</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewServeMux</span><span class="p">()</span>
	<span class="n">mux</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/foo"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">_</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"/foo failed during write"</span><span class="p">,</span> <span class="s">"error"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">})</span>
	<span class="n">httpServer</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">http</span><span class="o">.</span><span class="n">Server</span><span class="p">{</span><span class="n">Handler</span><span class="o">:</span> <span class="n">mux</span><span class="p">}</span>

	<span class="c">// Start HTTP serve and shutdown goroutines.</span>
	<span class="n">eg</span><span class="o">.</span><span class="n">Go</span><span class="p">(</span><span class="k">func</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> <span class="c">// HTTP serve.</span>
		<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"HTTP server starting at port :%d"</span><span class="p">,</span> <span class="o">*</span><span class="n">httpPort</span><span class="p">))</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">httpServer</span><span class="o">.</span><span class="n">Serve</span><span class="p">(</span><span class="n">httpListener</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">errors</span><span class="o">.</span><span class="n">Is</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">ErrServerClosed</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"HTTP server failed"</span><span class="p">,</span> <span class="s">"error"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
			<span class="k">return</span> <span class="n">err</span>
		<span class="p">}</span>
		<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"HTTP server shut down"</span><span class="p">)</span>
		<span class="k">return</span> <span class="no">nil</span>
	<span class="p">})</span>
	<span class="n">eg</span><span class="o">.</span><span class="n">Go</span><span class="p">(</span><span class="k">func</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> <span class="c">// HTTP graceful -&gt; forceful shutdown.</span>
		<span class="o">&lt;-</span><span class="n">gCtx</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span>
		<span class="n">shutdownCtx</span><span class="p">,</span> <span class="n">shutdownCancel</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">WithTimeout</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">(),</span> <span class="n">httpShutdownTimeout</span><span class="p">)</span>
		<span class="k">defer</span> <span class="n">shutdownCancel</span><span class="p">()</span>
		<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"HTTP server gracefully shutting down"</span><span class="p">)</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">httpServer</span><span class="o">.</span><span class="n">Shutdown</span><span class="p">(</span><span class="n">shutdownCtx</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"HTTP server shutdown failed. forcefully shutting down"</span><span class="p">)</span>
			<span class="k">return</span> <span class="n">errors</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="n">httpServer</span><span class="o">.</span><span class="n">Close</span><span class="p">())</span>
		<span class="p">}</span>
		<span class="k">return</span> <span class="no">nil</span>
	<span class="p">})</span>

	<span class="c">// gRPC server.</span>
	<span class="n">grpcServer</span> <span class="o">:=</span> <span class="n">grpc</span><span class="o">.</span><span class="n">NewServer</span><span class="p">()</span>
	<span class="n">pb</span><span class="o">.</span><span class="n">RegisterGreeterServer</span><span class="p">(</span><span class="n">grpcServer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fooService</span><span class="p">{})</span>

	<span class="c">// Start gRPC serve and shutdown goroutines.</span>
	<span class="n">eg</span><span class="o">.</span><span class="n">Go</span><span class="p">(</span><span class="k">func</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> <span class="c">// gRPC serve.</span>
		<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"gRPC server starting at port :%d"</span><span class="p">,</span> <span class="o">*</span><span class="n">grpcPort</span><span class="p">))</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">grpcServer</span><span class="o">.</span><span class="n">Serve</span><span class="p">(</span><span class="n">grpcServerListener</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"gRPC server failed"</span><span class="p">,</span> <span class="s">"error"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
			<span class="k">return</span> <span class="n">err</span>
		<span class="p">}</span>
		<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"gRPC server shut down"</span><span class="p">)</span>
		<span class="k">return</span> <span class="no">nil</span>
	<span class="p">})</span>
	<span class="n">eg</span><span class="o">.</span><span class="n">Go</span><span class="p">(</span><span class="k">func</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span> <span class="c">// gRPC graceful -&gt; forceful shutdown.</span>
		<span class="o">&lt;-</span><span class="n">gCtx</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span>
		<span class="n">stopped</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="k">struct</span><span class="p">{})</span>
		<span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
			<span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"gRPC server gracefully shutting down"</span><span class="p">)</span>
			<span class="n">grpcServer</span><span class="o">.</span><span class="n">GracefulStop</span><span class="p">()</span>
			<span class="nb">close</span><span class="p">(</span><span class="n">stopped</span><span class="p">)</span>
		<span class="p">}()</span>
		<span class="k">select</span> <span class="p">{</span>
		<span class="k">case</span> <span class="o">&lt;-</span><span class="n">stopped</span><span class="o">:</span>
		<span class="k">case</span> <span class="o">&lt;-</span><span class="n">time</span><span class="o">.</span><span class="n">After</span><span class="p">(</span><span class="n">grpcShutdownTimeout</span><span class="p">)</span><span class="o">:</span>
			<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"gRPC server forcefully shutting down"</span><span class="p">)</span>
			<span class="n">grpcServer</span><span class="o">.</span><span class="n">Stop</span><span class="p">()</span>
			<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"gRPC server was unable to gracefully shut down"</span><span class="p">)</span>
		<span class="p">}</span>
		<span class="k">return</span> <span class="no">nil</span>
	<span class="p">})</span>

	<span class="c">// This returns nil if shutdown was successful, or the first err for any</span>
	<span class="c">// shutdown issue.</span>
	<span class="k">return</span> <span class="n">eg</span><span class="o">.</span><span class="n">Wait</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">type</span> <span class="n">fooService</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">pb</span><span class="o">.</span><span class="n">UnimplementedGreeterServer</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s run it and send it a SIGINT:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>go run <span class="nb">.</span>
2026/08/18 09:19:39 INFO HTTP server starting at port :7000
2026/08/18 09:19:39 INFO gRPC server starting at port :8000
^C2026/08/18 09:19:40 INFO gRPC server gracefully shutting down
2026/08/18 09:19:40 INFO HTTP server gracefully shutting down
2026/08/18 09:19:40 INFO HTTP server shut down
2026/08/18 09:19:40 INFO gRPC server shut down
2026/08/18 09:19:40 INFO application exited with 0
</code></pre></div></div>]]></content><author><name></name></author><category term="go" /><category term="kubernetes" /><category term="grpc" /><summary type="html"><![CDATA[A quick example of how to do graceful shutdown of your gRPC and HTTP server in Go, particularly useful on Kubernetes.]]></summary></entry><entry><title type="html">Google styleguide code review skills</title><link href="https://jeanbza.github.io/ai/claude/cpp/go/python/2026/08/13/google-codereview-skills.html" rel="alternate" type="text/html" title="Google styleguide code review skills" /><published>2026-08-13T08:55:23+00:00</published><updated>2026-08-13T08:55:23+00:00</updated><id>https://jeanbza.github.io/ai/claude/cpp/go/python/2026/08/13/google-codereview-skills</id><content type="html" xml:base="https://jeanbza.github.io/ai/claude/cpp/go/python/2026/08/13/google-codereview-skills.html"><![CDATA[<p>At Google, code changes require both a language readability and a domain review.
The former is what you’d expect from a code review, whereas the latter was a
review from someone[^1] very proficient in the language, intended to help drive best
practices of that language across the company, and sometimes to drive
conformance towards a certain style of the language.</p>

<p>Each language maintained a style guide: a list of best practice and style
decisions, curated by the hundreds or thousands of readability experts for that
language.</p>

<p>I was a C++ and Go readability reviewer, and contributed some tidbits to the Go
styleguide, as well as to many discussions about how to write Go. The styleguide
was a great place to put best practices for the language, but also a great place
to go to learn, since best practices were usually followed with an explanation.
That way, newcomers to the language could ask for a change and simultaneously
reference the styleguide as the high-quality explanation for the change request.</p>

<p>Google has <em>very</em> generously open sourced the styleguides for its languages at
<a href="https://google.github.io/styleguide/">https://google.github.io/styleguide/</a>. I
highly encourage practitioners of any of the languages there to give them a
read. I can greatly recommend the C++ and Go ones, but I’m sure the others are
good too.</p>

<p>For the last 6 months at Netflix, when asking Claude to write code, I’ve found
it varying degrees of helpful to have Claude read those styleguides, and apply
edits based on advice in them. It’s not been perfect - far from it,
unfortunately. But it’s generally been better than not having it do so.</p>

<p>So, I thought I’d open source it:
<a href="https://github.com/jeanbza/claude-extensions">https://github.com/jeanbza/claude-extensions</a>.</p>

<p>To install it, simply run the following inside a Claude session:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/plugin marketplace add jeanbza/claude-extensions
/plugin install google-go-review@jeanbza
/reload-plugins
</code></pre></div></div>

<p>To use it, simply run <code class="language-plaintext highlighter-rouge">/jeanbza:google-go-review</code> (or cpp, or py - the other
two I’ve added so far).</p>

<p>There’s more for me to do, in terms of prompt engineering: Claude frequently
does a poor job at <a href="https://google.github.io/styleguide/go/decisions#handle-errors">“Handle errors”</a>
for example. (My pet theory is that Claude doesn’t bother to check the
signatures) I’ll keep working on it, both for myself and whomever else wants to
use it. I’m also working on a project to go a bit lower down the stack and try
to inject some evaluators into its reward model with lifecycle hooks. But for
now, I hope it’s helpful to you as it’s been for me!</p>]]></content><author><name></name></author><category term="ai" /><category term="claude" /><category term="cpp" /><category term="go" /><category term="python" /><summary type="html"><![CDATA[A plugin containing skills for Claude to perform Google's styleguide reviews.]]></summary></entry><entry><title type="html">Reproducible, AI-driven large scale changes</title><link href="https://jeanbza.github.io/ai/infrastructure/2026/08/08/ai-det-stoch.html" rel="alternate" type="text/html" title="Reproducible, AI-driven large scale changes" /><published>2026-08-08T08:55:23+00:00</published><updated>2026-08-08T08:55:23+00:00</updated><id>https://jeanbza.github.io/ai/infrastructure/2026/08/08/ai-det-stoch</id><content type="html" xml:base="https://jeanbza.github.io/ai/infrastructure/2026/08/08/ai-det-stoch.html"><![CDATA[<p>Large scale code changes (LSCs) are groups of code changes that all try to
accomplish the same goal, across a huge number of codebases.</p>

<p>For example, a common LSC is to migrate all codebases at a company from
libraryX@v1 to libraryX@v2, changing code along the way to account for
differences in API between v1 and v2. Due to their breadth, LSCs have high
cognition cost, owing to the high amount of contexts you have to reason about.
But also due to their breadth, they tend to have bounded problem spaces: the
wider a change has to be applied, the more generic the change must be. A change
that can’t be generically expressed is not an LSC, but instead a series of
individual changes.</p>

<p>AI agents are one way to attempt to tackle LSCs. Goals can be expressed
generically and handed to AIs as prompts to work on all target codebases. But,
when AI agents are set loose to perform LSCs, LSCs’ high cognition cost
compounds with AI non-determinism to result in low confidence in the changes
being applied, incur high costs, and produce results which are unauditable.</p>

<p>This article presents a different approach, based on a simple observation: most
changes that can be expressed generically can also be expressed mechanically. AI
agents explore and solve the LSC’s problem space in an evaluation flywheel, and
encode their results in a metaprogramming program which <em>itself</em> is used to
perform the LSC in a way that is deterministic, leads to higher confidence, is
cheap, and is as auditable as ordinary source code.</p>

<p><img src="/assets/agent_exploration.gif" alt="Explore" /></p>

<h2 id="context">Context</h2>

<p>At Google and now at Netflix, I find myself frequently having to perform large
scale changes to code: changes to hundreds or thousands of codebases. For
example, at Google, when I rewrote the internal Go <code class="language-plaintext highlighter-rouge">status</code> library (<a href="https://pkg.go.dev/google.golang.org/grpc/internal/status">similar
external version
here</a>) to support <a href="https://go.dev/blog/go1.13-errors">Go
1.13 error wrapping/unwrapping</a>, Google’s
internal one-version-of-every-library + monorepo setup meant that I had to also
go make this work for ~56k Go programs. That involved making changes to hundreds
of programs with non-conforming / Hyrum’s-Law type oddities at the same time.</p>

<p>Most recently at Netflix, I’ve been working on problems requiring LSCs over our
thousands of Go projects. An example recent, long-running LSC is periodic
remediation of vulnerable dependencies.</p>

<p>Doing this kind of work in 2026 is dramatically faster with AI Agents, but
surprisingly there’s a good lesson to be learned from the old way of doing it
which can be paired with AI agents.</p>

<p>Before AI agents, we used to perform these changes by hand, and quickly learned
instead to build metaprogramming programs: programs which modified programs.</p>

<p>With AI agents, it’s natural to think to replace this with hordes of AI
sub-agents performing code change on each repository, with some shared prompt or
goal. However, it turns out to still be more advantageous to write a program for
the LSC, albeit now with an AI agent to explore the problem space and encode the
solution into that program.</p>

<p>The end result is a small program which, when run on all the target
repositories, solves the LSC goal in a deterministic, cheap, and auditable
manner.</p>

<p>Let’s take a look in closer detail.</p>

<h2 id="evaluation-framework">Evaluation framework</h2>

<p>This technique starts with an evaluation framework and a discovery / actuate /
evaluate loop. Using the vulnerability remediation project above, the evaluation
framework is largely self-evident. The goal is to remediate all Go repositories
of vulnerable dependencies. For each repository,</p>

<ul>
  <li>Go’s <a href="https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck">govulncheck</a>
reports whether there are any vulnerabilities. (Goal: 0 fixable vulnerabilities)</li>
  <li>The output and exit code of whatever program we write tells us whether we were
successful, and if not what failed. (Goal: no failures)</li>
  <li>A PR’s CI/CD log tells us whether a PR to perform that remediation is valid,
and if not what failed. (Goal: valid PR)</li>
</ul>

<p>This is all we need to begin our loop.</p>

<h2 id="discovery-loop">Discovery loop</h2>

<p>With a sufficiently large enough set of repositories (which we have at Netflix,
or is generally available in GitHub), and enough compute, and enough tokens, and
a bounded problem space, we can solve the problem space and produce a single
solution.</p>

<p>We produce a solution to the problem space in the form of a program. That
program remediates vulnerabilities in whatever repository it is run in.</p>

<p>We encode the problem space, as we discover it, as a series of <a href="https://pkg.go.dev/golang.org/x/tools/txtar">txtar
tests</a>. That becomes the early
signal in our evaluation framework; it doubles up as a regression test
mechanism; and it triples up as audit documentation (This is how the program is
built to behave under scenario X, Y, Z).</p>

<p>As the agent explores the space, it encodes new situations it finds as txtar
tests, writes code to solve the expanded problem scope, verifies it first
against the txtar test and then by attempting to send PRs to its test repository
cohort, looking at failure and build logs, and re-assessing. When it reaches
quiescence, it expands its test cohort exponentially.</p>

<p>When we applied this strategy to the vulnerability remediation problem, encoding
the problem space as txtar texts, we found fewer and fewer new cases as we
expanded the test pool:</p>

<p><img src="/assets/discovery_curve.jpg" alt="Discovery curve" /></p>

<h2 id="deterministic-solution">Deterministic solution</h2>

<p>The end result is a single program which can be run on a repository to
deterministically upgrade dependencies to remediate vulnerabilities. And since
it’s a program and not an LLM, we also benefit being able to audit its behaviour
through normal source control history. Both the determinism and auditability
increase confidence in its use in large scale changes considerably.</p>

<p>The other major benefit is cost and time: the program runs in &lt;2s, and costs
$0+a bit of CPU.</p>

<p>I ran a benchmark against 50 repositories, using the program in one test and an
AI agent (Claude Sonnet 5, medium effort) in the other set, both tasked with
remediating vulnerabilities. On average, the AI agent took ~50k<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> tokens
($0.15/run) and 4m<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>/run. Across ~2000 of Go repos, that’s $750/run. We run
this large scale change every 6h to remediate vulnerabilities quickly: that
means we’d pay ~$0.66M/yr. At Google’s scale with 30x-40x those Go repos, that’s
more like $20M/yr.</p>

<p><img src="/assets/ai_agent_vs_program_cost.jpg" alt="AI cost for LSCs" /></p>

<p>And, that’s a minimum: as developers 10x their velocity, they also 10x the repos
they create, which 10x this cost. The program by comparison is ~$0 and will
forever stay that way.</p>

<h2 id="long-tail">Long tail</h2>

<p>There may be a long tail of issues to deal with. In the cases we’ve dealt with
so far, this has not been the case, but it’s fair to imagine that there might
be.</p>

<p>Of course, two simple solutions exist:</p>

<ul>
  <li>Run an AI agent as a backstop.</li>
  <li>Keep the discovery loop infrastructure and allow long-tail findings to
continually improve the program.</li>
</ul>

<h2 id="a-better-solution">A better solution</h2>

<p>It’s attractive to throw AI Agents at every problem, since they’re so fast at
exploring problems at producing solutions. But in the case of LSCs, the way that
you apply AI Agents makes a difference.</p>

<p>At Netflix we’ve had good success marrying the speed and ability of AI agents to
explore the unknown, along with the observation that most LSCs have bounded
problem spaces whose solutions are generically and mechanically expressable, to
have AI agents create LSC programs.</p>

<p>The result is a solution that runs in seconds instead of hours, costs nothing
instead of hundreds of thousands a year, behaves the same way on the nth
repository as it did on the first, and can be reviewed the way we review
everything else: by reading the source and the logs.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>50k is the rough median. Low end was 28k, high end was 77k. Most were in the 40k-60k range. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>4m is the rough median. Low end was 2m16s, high end was 8m53s. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="ai" /><category term="infrastructure" /><summary type="html"><![CDATA[Making AI-driven large scale code changes deterministic, cheap, and auditable.]]></summary></entry><entry><title type="html">Queues in Postgres</title><link href="https://jeanbza.github.io/infrastructure/2026/05/06/postgres-queue.html" rel="alternate" type="text/html" title="Queues in Postgres" /><published>2026-05-06T08:55:23+00:00</published><updated>2026-05-06T08:55:23+00:00</updated><id>https://jeanbza.github.io/infrastructure/2026/05/06/postgres-queue</id><content type="html" xml:base="https://jeanbza.github.io/infrastructure/2026/05/06/postgres-queue.html"><![CDATA[<p>At Netflix, part of the infrastructure I work on is a distributed priority queue
built on Redis. It’s great in many ways, but also pretty expensive and complex.</p>

<p>I’ll detail that another time. For now, I’m going to focus on a much simpler way
to solve this if you have lesser constraints than what we do. This is based on
Postgres but would work for most relational databases. I’ve not load tested this
and couldn’t really tell you how it scales, but it should be “good enough” for
most queueing under ~1k QPS and &lt;1M messages (and probably fair amount higher,
but again, haven’t load tested).</p>

<p>Note: This was used at our
<a href="https://github.com/Netflix-Skunkworks/golang-index">golang-index</a> as well as in
<a href="https://github.com/golang/pkgsite/commit/372618454cdb62e4cbaab1fd14c58f2faf5db80a">Go’s pkgsite Postgres queue</a>.</p>

<h2 id="schema">Schema</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- This is the queue table.</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">queue_tasks</span> <span class="p">(</span>
    <span class="c1">-- Bookkeeping.</span>
    <span class="n">id</span>          <span class="n">BIGSERIAL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
    
    <span class="c1">-- Each consumer/dequeue-er needs a unique task name. appID+threadID for example.</span>
    <span class="n">task_name</span>   <span class="nb">TEXT</span> <span class="k">UNIQUE</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
    
    <span class="c1">-- [...] Whatever payload you need for each message goes here.</span>

    <span class="c1">-- Queueing.</span>
    <span class="n">created_at</span>  <span class="n">TIMESTAMPTZ</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="n">NOW</span><span class="p">(),</span>
    <span class="n">started_at</span>  <span class="n">TIMESTAMPTZ</span>

    <span class="c1">-- Optionally also add completed_at and maybe success/failure columns if you</span>
    <span class="c1">-- want a history; otherwise just delete on finish.</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">idx_queue_tasks_started_created</span>
<span class="k">ON</span> <span class="n">queue_tasks</span> <span class="p">(</span><span class="n">started_at</span><span class="p">,</span> <span class="n">created_at</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="enqueueing">Enqueueing</h2>

<p>Enqueue with an atomic test-and-set query:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">queue_tasks</span> <span class="p">(...)</span> <span class="c1">-- All but created_at.</span>
<span class="k">VALUES</span> <span class="p">(...)</span>
<span class="k">ON</span> <span class="n">CONFLICT</span> <span class="p">(</span><span class="n">task_name</span><span class="p">)</span> <span class="k">DO</span> <span class="k">NOTHING</span><span class="p">;</span> <span class="c1">-- Another consumer won the race; no-op.</span>
</code></pre></div></div>

<h2 id="dequeueing">Dequeueing</h2>

<p>Dequeue with a test for stalled workers:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="n">next_task</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="n">id</span>
    <span class="k">FROM</span> <span class="n">queue_tasks</span>
    <span class="k">WHERE</span> <span class="n">started_at</span> <span class="k">IS</span> <span class="k">NULL</span>
        <span class="c1">-- Allow accepting work from stalled workers.</span>
       <span class="k">OR</span> <span class="n">started_at</span> <span class="o">+</span> <span class="n">INTERVAL</span> <span class="s1">'5 minutes'</span> <span class="o">&lt;</span> <span class="n">NOW</span><span class="p">()</span>
    <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">created_at</span> <span class="k">ASC</span>
    <span class="k">LIMIT</span> <span class="mi">1</span>
    <span class="k">FOR</span> <span class="k">UPDATE</span> <span class="n">SKIP</span> <span class="n">LOCKED</span>
<span class="p">)</span>
<span class="k">UPDATE</span> <span class="n">queue_tasks</span>
<span class="k">SET</span> <span class="n">started_at</span> <span class="o">=</span> <span class="n">NOW</span><span class="p">()</span>
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">id</span> <span class="k">FROM</span> <span class="n">next_task</span><span class="p">)</span>
<span class="n">RETURNING</span> <span class="n">id</span><span class="p">,</span> <span class="n">started_at</span>
</code></pre></div></div>

<p>And then to complete work, you’d simply <code class="language-plaintext highlighter-rouge">DELETE</code> the row.</p>

<h3 id="with-priority">With priority</h3>

<p>Simple add a <code class="language-plaintext highlighter-rouge">priority</code> field of your choosing and then <code class="language-plaintext highlighter-rouge">ORDER BY priority</code>
instead of <code class="language-plaintext highlighter-rouge">ORDER BY created_at</code>.</p>

<h3 id="with-batch-dequeue">With batch dequeue</h3>

<p>To batch dequeue simply change <code class="language-plaintext highlighter-rouge">LIMIT 1</code> to <code class="language-plaintext highlighter-rouge">LIMIT n</code>.</p>

<h3 id="with-lease-extension">With lease extension</h3>

<p>If you need lease extension, you can add <code class="language-plaintext highlighter-rouge">leased_until</code> and change the stall check
to:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="n">next_task</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="n">id</span>
    <span class="k">FROM</span> <span class="n">queue_tasks</span>
    <span class="k">WHERE</span> <span class="n">started_at</span> <span class="k">IS</span> <span class="k">NULL</span>
        <span class="c1">-- Allow accepting work from stalled workers; but now we used</span>
        <span class="c1">-- leased_until, which workers are expected to periodically lease</span>
        <span class="c1">-- extend.</span>
       <span class="k">OR</span> <span class="n">leased_until</span> <span class="o">&lt;</span> <span class="n">NOW</span><span class="p">()</span>
    <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">created_at</span> <span class="k">ASC</span>
    <span class="k">LIMIT</span> <span class="mi">1</span>
    <span class="k">FOR</span> <span class="k">UPDATE</span> <span class="n">SKIP</span> <span class="n">LOCKED</span>
<span class="p">)</span>
<span class="k">UPDATE</span> <span class="n">queue_tasks</span>
<span class="k">SET</span> <span class="n">started_at</span> <span class="o">=</span> <span class="n">NOW</span><span class="p">(),</span>
    <span class="n">leased_until</span> <span class="o">=</span> <span class="n">NOW</span><span class="p">()</span> <span class="o">+</span> <span class="n">INTERVAL</span> <span class="s1">'5 minutes'</span> <span class="c1">-- Or however long you want leases.</span>
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">id</span> <span class="k">FROM</span> <span class="n">next_task</span><span class="p">)</span>
<span class="n">RETURNING</span> <span class="n">id</span><span class="p">,</span> <span class="n">started_at</span>
</code></pre></div></div>

<p>And then have your workers periodically update <code class="language-plaintext highlighter-rouge">leased_until</code> to some time in
the near future.</p>

<h3 id="with-max-consumers">With max consumers</h3>

<p>If you need to limit work to a maximum amount of in-flight work/consumers, you can do:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="n">active_workers</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">current_count</span>
    <span class="k">FROM</span> <span class="n">queue_tasks</span>
    <span class="k">WHERE</span> <span class="n">started_at</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span>
      <span class="k">AND</span> <span class="n">started_at</span> <span class="o">+</span> <span class="n">INTERVAL</span> <span class="s1">'5 minutes'</span> <span class="o">&gt;=</span> <span class="n">NOW</span><span class="p">()</span>
<span class="p">),</span>
<span class="n">next_task</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="n">id</span>
    <span class="k">FROM</span> <span class="n">queue_tasks</span>
    <span class="k">WHERE</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">current_count</span> <span class="k">FROM</span> <span class="n">active_workers</span><span class="p">)</span> <span class="o">&lt;</span> <span class="p">:</span><span class="n">n</span>
      <span class="k">AND</span> <span class="p">(</span><span class="n">started_at</span> <span class="k">IS</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="n">started_at</span> <span class="o">+</span> <span class="n">INTERVAL</span> <span class="s1">'5 minutes'</span> <span class="o">&lt;</span> <span class="n">NOW</span><span class="p">())</span>
    <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">created_at</span> <span class="k">ASC</span>
    <span class="k">LIMIT</span> <span class="mi">1</span>
    <span class="k">FOR</span> <span class="k">UPDATE</span> <span class="n">SKIP</span> <span class="n">LOCKED</span>
<span class="p">)</span>
<span class="k">UPDATE</span> <span class="n">queue_tasks</span>
<span class="k">SET</span> <span class="n">started_at</span> <span class="o">=</span> <span class="n">NOW</span><span class="p">()</span>
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">id</span> <span class="k">FROM</span> <span class="n">next_task</span><span class="p">)</span>
<span class="n">RETURNING</span> <span class="n">id</span><span class="p">,</span> <span class="n">started_at</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>SQL databases are pretty easy to turn into queueing databases, and it’s
fairly easy to tweak your SQL scripts to make the queue behave the way you want
them to.</p>

<p>If you already rely on a SQL database and need to add queueing, consider just
doing it in your pre-existing SQL database rather than adding a whole new system
dependency.</p>]]></content><author><name></name></author><category term="infrastructure" /><summary type="html"><![CDATA[Building a distributed priority queue on Postgres.]]></summary></entry><entry><title type="html">Inlining function code</title><link href="https://jeanbza.github.io/design/2025/10/23/function-spectrum.html" rel="alternate" type="text/html" title="Inlining function code" /><published>2025-10-23T08:55:23+00:00</published><updated>2025-10-23T08:55:23+00:00</updated><id>https://jeanbza.github.io/design/2025/10/23/function-spectrum</id><content type="html" xml:base="https://jeanbza.github.io/design/2025/10/23/function-spectrum.html"><![CDATA[<p><em>Preface: John Carmack’s
<a href="https://cbarrete.com/carmack.html">email on inlining code</a> on this subject was
a really impactful read for me. I highly recommend it.</em></p>

<p>I’ve lately been working with a new college grad on my team, and he asked a good
question about how often we should be abstracting code into functions. I thought
about it a while and came up with this answer. I’m somewhat satisfied by it, but
if you have a better way to frame this, please reach out to let me know.</p>

<h2 id="cost">Cost</h2>

<p>All abstractions come with a <strong>cost</strong>:</p>

<ul>
  <li>They introduce complexity.</li>
  <li>They demand context switching.
    <ul>
      <li>For functions: This cost can be very high when trying to understand a
  high-level function that requires context switching between many nested
  sub-, sub-sub-, sub-sub-sub-, […] -functions.</li>
    </ul>
  </li>
  <li>They increase the size of the mental map required to understand the code.</li>
  <li>They may make debugging harder (mostly as a result of all the above).</li>
  <li>They may impact build and runtime performance.</li>
  <li>See also: <a href="https://www.youtube.com/watch?v=rHIkrotSwcc">there are no zero-cost abstractions</a>.</li>
</ul>

<p>So, their cost should be justified by their value.</p>

<h2 id="objective-value">Objective value</h2>

<p>Functions are <strong>objectively valuable</strong> in a few situations:</p>

<ul>
  <li>When they’re used to deduplicate logic (my personal bar is logic found in
3 places, but some people go for 2).</li>
  <li>When they’re used to extract some logic that needs to be called from
elsewhere (ex a library; although this often falls into the prior bullet).</li>
  <li>When they’re used to extract some logic that you want to individually
test.</li>
  <li>(There are probably a few other objectively useful cases)</li>
</ul>

<h2 id="subjective-value">Subjective value</h2>

<p>Functions are <strong>subjectively valuable</strong> when they’re used just for the sake of
abstraction. That subjectivity is what this article is about. Let’s dive into
that subjectivity.</p>

<p>Let’s imagine a 1000 line program whose logic has no objective reasons to
introduce functions: there’s no duplication, there’s no logic that needs to be
called from elsewhere, there are no tests, etc.</p>

<p>One type of person might prefer 0 functions: a single main func that is 1000
lines long, each line a valuable statement of business logic. Every thing the
program does can immediately be grokked in perfect context. Debugging is
extremely easy: you can go to the faulting line and perfectly work backwards to
see how you arrived at the fault. And, it’s hard to misuse sub-functions: there are none!</p>

<p>Another type of person might prefer so many functions that every business logic
line gets its own function, the caller of which gets its own function, the
caller of which gets its own function, […], and main is just 2-5 function
calls long. (This used to be a really popular thing in Javascript and Ruby, but
I’ve noticed
<a href="https://rubystyle.guide/#no-single-line-methods">a lot of pushback on that kind of thing recently</a>)
The advantage here is that it’s really easy to get a quick, vague sense of what
main does: A, B, C. Ok but what <em>actually</em> does A do? Oh, it does x, y, z. Ok…
but what does x <em>actually</em> do? Oh, it does i, ii, iii, and iv. Ok….. what does
i <em>actually</em> do? […] Oh, it sends a GET request to /foo.</p>

<p>So those are two extremes. In between the extremes is a spectrum.</p>

<p>I think the people who tend to want to understand a program <em>completely</em> are
more on the top part of the spectrum (less abstractions to dig through when
trying to understand <em>completely</em>) and people who tend to want to understand a
program <em>quickly</em> are more on the bottom part of the spectrum (more abstractions
to gain vague understanding quickly).</p>

<h2 id="illustrating-subjectivity">Illustrating subjectivity</h2>

<p>To illustrate that a little more:</p>

<p>Person #1 wants to understand program #2 <em>completely</em>. Because of all the
abstractions, he has to do a depth first search and collect all leaf nodes (the
valuable business logic) into a mental list before he’s able to follow a path
from main start to main end. (And, every time he returns, he has to perform some
of that DFS again as the mapping inevitably falls out of his mind each time he
context switches)</p>

<p>Person #2 wants to understand program #1 <em>quickly</em>. Because of the lack of
abstractions, she has to try to map sections of code into logical groupings in
her mind, and can only reason about the order of those groupings mentally. (She
may make a series of notes or flow charts outside of code to help cope) (And,
every time she returns, she has to perform some re-grouping as the mapping
inevitably falls out of her head each time she context switches)</p>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[When to inline code and when to extract it.]]></summary></entry><entry><title type="html">A jj script to lint your entire graph</title><link href="https://jeanbza.github.io/jj/tooling/2025/10/17/jj-cleanall.html" rel="alternate" type="text/html" title="A jj script to lint your entire graph" /><published>2025-10-17T08:55:23+00:00</published><updated>2025-10-17T08:55:23+00:00</updated><id>https://jeanbza.github.io/jj/tooling/2025/10/17/jj-cleanall</id><content type="html" xml:base="https://jeanbza.github.io/jj/tooling/2025/10/17/jj-cleanall.html"><![CDATA[<p>This is the second of a series of posts about <code class="language-plaintext highlighter-rouge">jj</code>. The first is
<a href="/2025/10/06/jj-bash-it.html">A jj plugin for bash-it</a>.</p>

<hr />

<p>At Netflix, we use <a href="https://github.com/diffplug/spotless">the spotless linter</a>.
The main gripe I have with it is that it’s not integrated with any of our IDEs.
In contrast, in Go, <code class="language-plaintext highlighter-rouge">gofmt</code> is integrated with all my IDEs and linting happens
as I program; with spotless I have to remember to run <code class="language-plaintext highlighter-rouge">./gradlew spotlessApply</code>
before I push my changes, or else my PR’s build will break when it gets to the
lint check step.</p>

<p>When I’m working with <code class="language-plaintext highlighter-rouge">jj</code> rev chains, it’s annoying to
<code class="language-plaintext highlighter-rouge">jj edit &lt;rev&gt; &amp;&amp; ./gradlew spotlessApply</code> each and every rev before sending
them all off to be CI/CD’d with <code class="language-plaintext highlighter-rouge">jj git push --all</code>.</p>

<p>So, I made this little script. I hope you’ll find it useful too:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">set</span> <span class="nt">-e</span>
<span class="k">for </span>i <span class="k">in</span> <span class="sb">`</span>jj log <span class="nt">-r</span> <span class="s2">"mutable()"</span> <span class="nt">--no-graph</span> <span class="nt">-T</span> <span class="s1">'change_id ++ "\n"'</span> | <span class="nb">tac</span><span class="sb">`</span><span class="p">;</span> <span class="k">do
  </span>jj edit <span class="nv">$i</span>
  ./gradlew spotlessApply
  ./gradlew spotbugsMain
<span class="k">done
</span>jj log
</code></pre></div></div>

<p>Of course, sub out the for loop with whatever you want to do at each rev.</p>

<p>It makes use of <code class="language-plaintext highlighter-rouge">jj</code>’s
<a href="https://jj-vcs.github.io/jj/latest/templates/">fantastic templating language</a>
to walk the graph from oldgest to newest change, linting as it goes.</p>]]></content><author><name></name></author><category term="jj" /><category term="tooling" /><summary type="html"><![CDATA[A small script that walks every mutable rev in the jj graph and lints as it goes.]]></summary></entry><entry><title type="html">A jj plugin for bash-it</title><link href="https://jeanbza.github.io/jj/tooling/2025/10/06/jj-bash-it.html" rel="alternate" type="text/html" title="A jj plugin for bash-it" /><published>2025-10-06T08:55:23+00:00</published><updated>2025-10-06T08:55:23+00:00</updated><id>https://jeanbza.github.io/jj/tooling/2025/10/06/jj-bash-it</id><content type="html" xml:base="https://jeanbza.github.io/jj/tooling/2025/10/06/jj-bash-it.html"><![CDATA[<p>This is the first of a series of posts about <code class="language-plaintext highlighter-rouge">jj</code>. The next is
<a href="/2025/10/16/jj-cleanall.html">A jj script to lint your entire graph</a>.</p>

<hr />

<p>I’ve been using <a href="https://github.com/jj-vcs/jj"><code class="language-plaintext highlighter-rouge">jj</code></a> instead of <code class="language-plaintext highlighter-rouge">git</code>, and it’s
been great. One thing I really miss using <code class="language-plaintext highlighter-rouge">git</code>, though, is a nice terminal
prompt that I get from <a href="https://github.com/Bash-it/bash-it"><code class="language-plaintext highlighter-rouge">bash-it</code></a>. So, I
whipped up one for <code class="language-plaintext highlighter-rouge">jj</code>.</p>

<p><img src="/assets/jj.png" alt="jj prompt" /></p>

<p><em>Notice:</em> The prompt now prints |jj:<strong>ty</strong>mnqrpn - readme| and
|jj:<strong>s</strong>ttyyssy|. The coloured short letters (ty, s) are the <code class="language-plaintext highlighter-rouge">jj</code> short prefix,
the full 8 characters are the short rev, and the optional <code class="language-plaintext highlighter-rouge">- readme</code> show the
bookmark associated with the rev if one exists.</p>

<p>I thought I’d post it here if anyone else is looking for this:</p>

<ol>
  <li>
    <p>Create <code class="language-plaintext highlighter-rouge">~/.bash_it/custom/jj.plugins.bash</code> with:</p>

    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># ~/.bash_it/custom/jj.plugins.bash</span>

 <span class="c"># Set jj prompt to be enabled by default</span>
 <span class="nv">SCM_PROMPT_SHOW_JJ_PRIVATE_INFO</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">SCM_PROMPT_SHOW_JJ_PRIVATE_INFO</span><span class="k">:-</span><span class="nv">true</span><span class="k">}</span><span class="s2">"</span>

 <span class="c"># jj repo check</span>
 <span class="k">function </span>is_jj_repo <span class="o">{</span>
 <span class="k">if</span> <span class="o">[</span> <span class="nt">-d</span> <span class="s2">".jj"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
     return </span>0
 <span class="k">else
     return </span>1
 <span class="k">fi</span>
 <span class="o">}</span>

 <span class="c"># jj prompt</span>
 <span class="k">function </span>jj_prompt_info <span class="o">{</span>
 <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$SCM_PROMPT_SHOW_JJ_PRIVATE_INFO</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"true"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> is_jj_repo<span class="p">;</span> <span class="k">then
     </span><span class="nb">local </span>jj_info
     <span class="nb">local </span>short_hash
     <span class="nb">local </span>jj_bookmarks

     <span class="c"># Get an 8-character short hash and bookmarks</span>
     <span class="nv">short_hash</span><span class="o">=</span><span class="si">$(</span>jj log <span class="nt">-r</span> @ <span class="nt">--no-graph</span> <span class="nt">--template</span> <span class="s1">'change_id.short(8)'</span> 2&gt;/dev/null<span class="si">)</span>
     <span class="nv">jj_bookmarks</span><span class="o">=</span><span class="si">$(</span>jj log <span class="nt">-r</span> @ <span class="nt">--no-graph</span> <span class="nt">--template</span> <span class="s1">'bookmarks.map(|b| b.name())'</span> 2&gt;/dev/null<span class="si">)</span>

     <span class="c"># Exit if we couldn't get a hash</span>
     <span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$short_hash</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
     return
     fi</span>

     <span class="c"># --- FINAL DYNAMIC HIGHLIGHTING ---</span>
     <span class="c"># Get the shortest unique prefix using the template you found</span>
     <span class="nb">local </span><span class="nv">prefix</span><span class="o">=</span><span class="si">$(</span>jj log <span class="nt">-r</span> @ <span class="nt">--no-graph</span> <span class="nt">--template</span> <span class="s1">'self.change_id().shortest()'</span> 2&gt;/dev/null<span class="si">)</span>

     <span class="c"># Fallback: if the command fails, default to the first character</span>
     <span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$prefix</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
     </span><span class="nv">prefix</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">short_hash</span>:0:1<span class="k">}</span><span class="s2">"</span>
     <span class="k">fi</span>

     <span class="c"># Determine the rest of the hash by removing the prefix</span>
     <span class="nb">local </span><span class="nv">rest_of_hash</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">short_hash</span><span class="p">#</span><span class="nv">$prefix</span><span class="k">}</span><span class="s2">"</span>
     <span class="nb">local </span><span class="nv">colored_hash</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MAGENTA$prefix$WHITE$rest_of_hash</span><span class="s2">"</span>

     <span class="c"># Build the base info string: always start with the hash</span>
     <span class="nv">jj_info</span><span class="o">=</span><span class="s2">"</span><span class="nv">$colored_hash</span><span class="s2">"</span>

     <span class="c"># If bookmarks exist, append them</span>
     <span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$jj_bookmarks</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
     </span><span class="nv">jj_info</span><span class="o">=</span><span class="s2">"</span><span class="nv">$jj_info</span><span class="s2"> - </span><span class="nv">$jj_bookmarks</span><span class="s2">"</span>
     <span class="k">fi</span>

     <span class="c"># Wrap in parentheses</span>
     <span class="nv">jj_info</span><span class="o">=</span><span class="s2">"(</span><span class="nv">$jj_info</span><span class="s2">)"</span>

     <span class="c"># --- ASTERISK LOGIC (UNCHANGED) ---</span>
     <span class="nb">local </span><span class="nv">first_bookmark</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">jj_bookmarks</span><span class="p">%% *</span><span class="k">}</span><span class="s2">"</span>
     <span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$first_bookmark</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
     </span><span class="nb">local </span>any_diff
     <span class="nv">any_diff</span><span class="o">=</span><span class="si">$(</span>jj diff <span class="nt">--summary</span> <span class="nt">--from</span> <span class="s2">"</span><span class="k">${</span><span class="nv">first_bookmark</span><span class="k">}</span><span class="s2">@origin"</span> 2&gt;/dev/null<span class="si">)</span>
     <span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$any_diff</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
         </span><span class="nv">jj_info</span><span class="o">=</span><span class="s2">"</span><span class="nv">$jj_info</span><span class="s2">*"</span>
     <span class="k">fi
     fi

     </span><span class="nb">echo</span> <span class="nt">-e</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SCM_THEME_PROMPT_PREFIX</span><span class="k">}${</span><span class="nv">jj_info</span><span class="k">}${</span><span class="nv">SCM_THEME_PROMPT_SUFFIX</span><span class="k">}</span><span class="s2">"</span>
 <span class="k">fi</span>
 <span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <code class="language-plaintext highlighter-rouge">~/.bash_it/themes/&lt;theme&gt;/&lt;theme&gt;.bash</code> (ex
<code class="language-plaintext highlighter-rouge">~/.bash_it/themes/sexy/sexy.bash</code>), amending the prompt_command:</p>

    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">function </span>prompt_command<span class="o">()</span> <span class="o">{</span>
 <span class="nb">local </span><span class="nv">scm_info</span><span class="o">=</span><span class="s2">""</span>

 <span class="c"># 1. Check if it is a JJ repo first (Priority)</span>
 <span class="c"># We use the 'is_jj_repo' function defined in your plugin.</span>
 <span class="k">if </span><span class="nb">type </span>is_jj_repo &amp;&gt;/dev/null <span class="o">&amp;&amp;</span> is_jj_repo<span class="p">;</span> <span class="k">then</span>
     <span class="c"># If JJ, show ONLY JJ info (suppress Git)</span>
     <span class="nv">scm_info</span><span class="o">=</span><span class="s2">"</span><span class="se">\[</span><span class="nv">$CYAN</span><span class="se">\]\$</span><span class="s2">(jj_prompt_info)"</span>
        
 <span class="c"># 2. If not JJ, check if it is a Git repo</span>
 <span class="k">elif</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="si">$(</span>git branch 2&gt; /dev/null<span class="si">)</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
     <span class="c"># If Git, show " on " + Git info</span>
     <span class="nv">scm_info</span><span class="o">=</span><span class="s2">" on </span><span class="se">\[</span><span class="nv">$PURPLE</span><span class="se">\]\$</span><span class="s2">(parse_git_branch)"</span>
 <span class="k">fi</span>

 <span class="c"># Build the PS1 using the selected scm_info</span>
 <span class="nv">PS1</span><span class="o">=</span><span class="s2">"</span><span class="se">\[</span><span class="k">${</span><span class="nv">BOLD</span><span class="k">}${</span><span class="nv">MAGENTA</span><span class="k">}</span><span class="se">\]\u</span><span class="s2"> </span><span class="se">\[</span><span class="nv">$WHITE</span><span class="se">\]</span><span class="s2">at </span><span class="se">\[</span><span class="nv">$ORANGE</span><span class="se">\]\h</span><span class="s2"> </span><span class="se">\[</span><span class="nv">$WHITE</span><span class="se">\]</span><span class="s2">in </span><span class="se">\[</span><span class="nv">$GREEN</span><span class="se">\]\w\[</span><span class="nv">$WHITE</span><span class="se">\]</span><span class="k">${</span><span class="nv">scm_info</span><span class="k">}</span><span class="se">\[</span><span class="nv">$WHITE</span><span class="se">\]\n\$</span><span class="s2"> </span><span class="se">\[</span><span class="nv">$RESET</span><span class="se">\]</span><span class="s2">"</span>

 <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$SEXY_THEME_SHOW_PYTHON</span><span class="s2">"</span> <span class="o">=</span> <span class="nb">true</span> <span class="o">]</span> <span class="p">;</span> <span class="k">then
     </span><span class="nv">PS1</span><span class="o">=</span><span class="s2">"</span><span class="se">\[</span><span class="k">${</span><span class="nv">BOLD</span><span class="k">}${</span><span class="nv">WHITE</span><span class="k">}</span><span class="se">\]</span><span class="si">$(</span>env_prompt<span class="si">)</span><span class="s2"> "</span><span class="nv">$PS1</span>
 <span class="k">fi</span>
 <span class="o">}</span>
</code></pre></div>    </div>
  </li>
</ol>

<p>And then you get some nice <code class="language-plaintext highlighter-rouge">jj</code> prompts!</p>

<hr />

<p><br />I’m also partial to the following <code class="language-plaintext highlighter-rouge">jj config edit --user</code>:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[revsets]</span>
<span class="py">log</span> <span class="p">=</span> <span class="s">"present(@) | ancestors(mine() &amp; immutable_heads().., 2) | trunk()"</span>
</code></pre></div></div>

<p>That does a good job showing me <em>my</em> commits when I <code class="language-plaintext highlighter-rouge">jj log</code>.</p>]]></content><author><name></name></author><category term="jj" /><category term="tooling" /><summary type="html"><![CDATA[A bash-it plugin that surfaces jj repository state in your shell prompt.]]></summary></entry><entry><title type="html">Storing protobuf-generated Go code without a registry</title><link href="https://jeanbza.github.io/protobuf/modules/2025/06/10/proto-storage.html" rel="alternate" type="text/html" title="Storing protobuf-generated Go code without a registry" /><published>2025-06-10T08:55:23+00:00</published><updated>2025-06-10T08:55:23+00:00</updated><id>https://jeanbza.github.io/protobuf/modules/2025/06/10/proto-storage</id><content type="html" xml:base="https://jeanbza.github.io/protobuf/modules/2025/06/10/proto-storage.html"><![CDATA[<p>Before diving into this topic, you may want to familiarize yourself with <a href="https://go.dev/blog/using-go-modules">Using Go Modules</a> and subsequent posts, or <a href="https://go.dev/ref/mod">the module spec</a>. Go modules work considerably differently than other languages’ dependency management schemes: of particular note to this article is that Go modules are comprised of the code that lives in VCS, as opposed to external registries like Artifactory, pypi, npmjs.org, and so on.</p>

<h2 id="local-protos">Local protos</h2>

<p>The following covers local protos that you own.</p>

<h3 id="that-you-expect-to-be-imported">That you expect to be imported</h3>

<p>If you expect others to import your proto, declare <code class="language-plaintext highlighter-rouge">option go_package</code> and generate Go bindings to that location.</p>

<p>This defines the single source of truth for Go generated code for your proto, making it easy for others to use your proto without needing to generate and store the Go bindings themselves.</p>

<p>For example:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">head </span>protos/server/server.proto
syntax <span class="o">=</span> <span class="s2">"proto3"</span><span class="p">;</span>

package server<span class="p">;</span>

option go_package <span class="o">=</span> <span class="s2">"github.com/username/myrepo/protos/server"</span><span class="p">;</span>
<span class="nv">$ </span>tree
<span class="nb">.</span>
|____protos
| |____server
| | |____server.pb.go
| | |____server.proto
</code></pre></div></div>

<p>Note: Public directories and releasing a tag. Remember that these Go bindings are intended to be import-able, so they should be in a non-<code class="language-plaintext highlighter-rouge">internal/</code> directory. And, if your repo has tagged releases, remember to tag a new release when you generate for the first time, so that others can begin depending on your proto-generated code.</p>

<h3 id="that-you-dont-expect-to-be-imported">That you don’t expect to be imported</h3>

<p>Even if you don’t expect others to import your proto, it’s still best to treat it as if it will be imported.</p>

<p>But, if you’re certain you’ll never want anybody else to import it, give it an <code class="language-plaintext highlighter-rouge">option go_package</code> that generates to an <code class="language-plaintext highlighter-rouge">internal/</code> directory in your Go module and add a comment above explaining that it is not meant to be depended upon.</p>

<p>For example:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">head </span>protos/server/server.proto
syntax <span class="o">=</span> <span class="s2">"proto3"</span><span class="p">;</span>

package server<span class="p">;</span>

// This proto is not intended to be depended upon, and as such always generates
// into an internal/ directory.
option go_package <span class="o">=</span> <span class="s2">"github.com/username/myrepo/internal/protogen/server"</span><span class="p">;</span>
<span class="nv">$ </span>tree
<span class="nb">.</span>
|____protos
| |____server
| | |____server.proto
|____internal
| |____protogen
| | |____server
| | | |____server.pb.go
</code></pre></div></div>

<h2 id="foreign-protos">Foreign protos</h2>

<p>The following covers foreign protos that you don’t own.</p>

<p>WARNING: Never copy <code class="language-plaintext highlighter-rouge">.protos</code>. What follows provides nuance about whether or not to store Go bindings for a proto in your module. However, you should never copy and store a foreign <code class="language-plaintext highlighter-rouge">.proto</code> in your repo.</p>

<h3 id="with-a-single-source-of-truth">With a single source of truth</h3>

<p>If a proto declares <code class="language-plaintext highlighter-rouge">option go_package</code> and provides its Go bindings at that location, that is the single source of truth for its generated Go code and you should import the single source of truth instead of generating and storing your own Go bindings.</p>

<p>If you are generating with <code class="language-plaintext highlighter-rouge">protoc</code>, that means you should not provide <code class="language-plaintext highlighter-rouge">--go_opt=M</code> for the proto.</p>

<p>If you are generating with <code class="language-plaintext highlighter-rouge">buf</code>, that means you should not include the proto in <code class="language-plaintext highlighter-rouge">managed.override</code>.</p>

<h4 id="looking-at-a-concrete-example">Looking at a concrete example</h4>

<p>Here’s a concrete example of that:</p>

<ul>
  <li><a href="https://github.com/googleapis/googleapis/blob/c759e924aa786f3df0e64499daf97d46a27edb31/google/cloud/speech/v1/cloud_speech.proto"><code class="language-plaintext highlighter-rouge">github.com/googleapis/google/cloud/speech/v1/cloud_speech.proto</code></a> is a proto that defines <code class="language-plaintext highlighter-rouge">option go_package = "cloud.google.com/go/speech/apiv1/speechpb;speechpb";</code>.</li>
  <li>Accordingly, its generated Go code exists at <a href="https://github.com/googleapis/google-cloud-go/tree/main/speech/apiv1/speechpb"><code class="language-plaintext highlighter-rouge">github.com/google-cloud-go/speech/apiv1/speechpb</code></a>.
    <ul>
      <li>Note: <code class="language-plaintext highlighter-rouge">cloud.google.com/go</code> is an alias for <code class="language-plaintext highlighter-rouge">github.com/googleapis/google-cloud-go</code>.</li>
    </ul>
  </li>
  <li>If you want to use it,
    <ul>
      <li>✅ You should import it as <code class="language-plaintext highlighter-rouge">speechpb cloud.google.com/go/speech/apiv1/speechpb</code>.</li>
      <li>❌ You should not generate (or store) <code class="language-plaintext highlighter-rouge">cloud_speech.pb.go</code>.</li>
    </ul>
  </li>
</ul>

<h4 id="what-if-a-proto-declares-option-go_package-but-i-cant-import-it">What if a proto declares option go_package, but I can’t import it?</h4>

<p>The proto is incorrectly configured. You should reach out to the proto owners to have them either remove the option go_package or generate the Go bindings at the expected location.</p>

<h3 id="without-a-single-source-of-truth">Without a single source of truth</h3>

<p>If a foreign proto does not declare <code class="language-plaintext highlighter-rouge">option go_package</code>, you have two options:</p>

<ol>
  <li>
    <p>If you can: convince the maintainers to add option go_package, generate their Go bindings at that location, and make it available in a Go module. This is the best option.</p>

    <p>However, this can be a big ask for non-Go teams that aren’t used to maintaining any Go code.And, if it’s a widely used proto there may need to be considerable thought given to how to migrate all existing dependers. So, it may be more pragmatic to:</p>
  </li>
  <li>
    <p>Otherwise: Generate and store their Go bindings in your module. This is risky, since it opens you and others up to the runtime panic described in Proto global registry.</p>

    <p>To mitigate that, store the Go bindings an <code class="language-plaintext highlighter-rouge">internal/</code> directory. If any of your code depends on these Go bindings and is accessible to other modules (is not in <code class="language-plaintext highlighter-rouge">main</code> or <code class="language-plaintext highlighter-rouge">_test</code> package), then it should also be in an <code class="language-plaintext highlighter-rouge">internal/</code> directory. See the discussion on transitive dependencies in Proto global registry.</p>
  </li>
</ol>

<p>Doing so won’t reduce your chance of running into this issue, but it does prevent anyone else accidentally depending on your copy of the Go bindings.</p>

<h2 id="proto-global-registry">Proto global registry</h2>

<p>All protocol buffers declarations linked into a Go binary are inserted into an <a href="https://protobuf.dev/reference/go/faq/#namespace-conflict">in-memory global registry</a>. If two protobuf declarations linked into a Go binary have the same name, then this leads to a namespace conflict. Worse, this error is a runtime error, making it easy to find its way into a deployment.</p>]]></content><author><name></name></author><category term="protobuf" /><category term="modules" /><summary type="html"><![CDATA[Where to store protobuf-generated Go code without the buf BSR.]]></summary></entry><entry><title type="html">Generating protobuf-generated Go code without a registry</title><link href="https://jeanbza.github.io/protobuf/2025/06/10/proto-generation.html" rel="alternate" type="text/html" title="Generating protobuf-generated Go code without a registry" /><published>2025-06-10T07:55:23+00:00</published><updated>2025-06-10T07:55:23+00:00</updated><id>https://jeanbza.github.io/protobuf/2025/06/10/proto-generation</id><content type="html" xml:base="https://jeanbza.github.io/protobuf/2025/06/10/proto-generation.html"><![CDATA[<p>I’ve recently had to generate Go code from protos, without the aid of a proto
registry. This article covers my advice for anyone needing to do the same.</p>

<h2 id="generating-go-code-with-protoc">Generating Go code with protoc</h2>

<p>Generating code with <code class="language-plaintext highlighter-rouge">protoc</code> is best for protos that have few or no
dependencies.</p>

<h3 id="simple-code-generation">Simple code generation</h3>

<p>Imagine two simple protos:</p>

<pre><code class="language-pb">// protos/user/user.proto
syntax = "proto3";

package user;

option go_package = "github.com/username/myrepo/protos/user";

message User {
    string name = 1;
}
</code></pre>

<pre><code class="language-pb">// protos/server/server.proto
syntax = "proto3";

package server;

option go_package = "github.com/username/myrepo/protos/server";

import "user/user.proto";

message Server {
    repeated user.User users = 1;
}
</code></pre>

<p>Generate Go code (.pb.gos) from this simple proto with <code class="language-plaintext highlighter-rouge">protoc</code>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>protoc protos/<span class="k">**</span>/<span class="k">*</span>.proto <span class="se">\</span>
    <span class="nt">--go_out</span><span class="o">=</span>protos <span class="se">\</span>
    <span class="nt">-I</span> protos/ <span class="se">\</span>
    <span class="nt">--go_opt</span><span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative
<span class="nv">$ </span>tree
<span class="nb">.</span>
|____protos
| |____server
| | |____server.pb.go
| | |____server.proto
| |____user
| | |____user.pb.go
| | |____user.proto
</code></pre></div></div>

<p>Warning: This approach allows others to depend on your generated Go code.</p>

<p>This example signs you up to maintaining generated Go code at <code class="language-plaintext highlighter-rouge">github.com/username/myrepo/protos/server</code> and <code class="language-plaintext highlighter-rouge">[...]/protos/user</code>. Whether or not this is a good idea is discussed in “Storing generated code”. For now suffice it to say that instead of declaring <code class="language-plaintext highlighter-rouge">option go_package</code> you could instead provide <code class="language-plaintext highlighter-rouge">--go_opt=M&lt;proto&gt;=&lt;go import&gt;</code>. And, instead of generating your <code class="language-plaintext highlighter-rouge">.pb.go</code>s into the publicly import-able <code class="language-plaintext highlighter-rouge">protos/</code>, you could generate them into an <code class="language-plaintext highlighter-rouge">internal/</code> directory which is not publicly import-able.</p>

<p>If you need to produce gRPC language bindings, add the gRPC option equivalents <code class="language-plaintext highlighter-rouge">--go-grpc_out</code> and <code class="language-plaintext highlighter-rouge">--go-grpc_opt</code>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>protoc protos/<span class="k">**</span>/<span class="k">*</span>.proto <span class="se">\</span>
    <span class="nt">--go_out</span><span class="o">=</span>protos <span class="se">\</span>
    <span class="nt">--go-grpc_out</span><span class="o">=</span>protos <span class="se">\</span>
    <span class="nt">-I</span> protos/ <span class="se">\</span>
    <span class="nt">--go_opt</span><span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\</span>
    <span class="nt">--go-grpc_opt</span><span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative
</code></pre></div></div>

<h3 id="foreign-protos">Foreign protos</h3>

<p>Part of your dependency graph may include protos in other repos:</p>

<div class="language-proto highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// protos/user/user.proto</span>
<span class="na">syntax</span> <span class="o">=</span> <span class="s">"proto3"</span><span class="p">;</span>

<span class="kn">package</span> <span class="nn">user</span><span class="p">;</span>

<span class="k">option</span> <span class="na">go_package</span> <span class="o">=</span> <span class="s">"github.com/username/myrepo/protos/user"</span><span class="p">;</span>

<span class="c1">// Not in this repo. Also not repo-rooted, which we'll discuss below. We could</span>
<span class="c1">// change this if we owned user.proto, but if this occurred in a foreign proto</span>
<span class="c1">// that we can't change then we'd have to work around it.</span>
<span class="k">import</span> <span class="s">"non/repo/rooted/import.proto"</span><span class="p">;</span>

<span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span>
    <span class="kt">string</span> <span class="na">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You’ll need to git clone the foreign protos and include them with <code class="language-plaintext highlighter-rouge">-I</code> during your <code class="language-plaintext highlighter-rouge">protoc</code> invocation.</p>

<p>You’ll may also need to account for quirks, such as:</p>

<ul>
  <li>
    <p>Dealing with imports not rooted at its repo root. Consider your code A importing B, and B importing another proto C, but not at C’s repo root. Instead, B imports C at at <code class="language-plaintext highlighter-rouge">$REPO-ROOT/some/nested/dir</code>. Since you don’t own B (another team / company does), you can’t change B. Get around this with <code class="language-plaintext highlighter-rouge">-I</code> rooted at the place the import expects, as shown below.</p>
  </li>
  <li>
    <p>Dealing with imports that do not define <code class="language-plaintext highlighter-rouge">option go_package</code>. When protos in your dependency graph don’t define <code class="language-plaintext highlighter-rouge">option go_package</code>, you’ll have to tell <code class="language-plaintext highlighter-rouge">protoc</code> which go_package to use with <code class="language-plaintext highlighter-rouge">--go_opt=M&lt;proto&gt;=&lt;go import path&gt;</code>. You’ll probably have to generate the foreign proto’s <code class="language-plaintext highlighter-rouge">.pb.go</code> into your module and point the aforementioned Go import path there, under the assumption that if they haven’t defined an option go_package then it’s also likely they are not generating and publishing Go bindings for you to use.</p>
  </li>
</ul>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">TMP</span><span class="o">=</span><span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span>
git clone https://github.com/foreignorg/foreignrepo.git <span class="s2">"</span><span class="nv">$TMP</span><span class="s2">/foreign"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> internal/protogen
protoc protos/<span class="k">**</span>/<span class="k">*</span>.proto <span class="se">\</span>
    non/repo/rooted/import.proto <span class="se">\</span>
    <span class="nt">--go_out</span><span class="o">=</span>internal/protogen <span class="se">\</span>
    <span class="nt">-I</span> protos <span class="se">\</span>
    <span class="nt">--go_opt</span><span class="o">=</span>Muser/user.proto<span class="o">=</span>github.com/username/myrepo/internal/protogen/user <span class="se">\</span>
    <span class="nt">--go_opt</span><span class="o">=</span>Mserver/server.proto<span class="o">=</span>github.com/username/myrepo/internal/protogen/server <span class="se">\</span>
    <span class="nt">-I</span> <span class="nv">$TMP</span>/path/to/expected/import/root <span class="se">\</span>
    <span class="nt">--go_opt</span><span class="o">=</span>Mnon/repo/rooted/import.proto<span class="o">=</span>github.com/foreignorg/foreignrepo/path/to/expected/import/root <span class="se">\</span>
    <span class="nt">--go_opt</span><span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative
</code></pre></div></div>

<p>Which when run would generate:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>tree
<span class="nb">.</span>
|____internal
| |____protogen
| | |____server
| | | |____server.pb.go
| | |____user
| | | |____user.pb.go
| | |____non
| | | |____repo
| | | | |____rooted
| | | | | |____import.pb.go
</code></pre></div></div>

<p>Note that all the generated code was generated into <code class="language-plaintext highlighter-rouge">internal/</code>. That involved overwriting <code class="language-plaintext highlighter-rouge">user.proto</code> and <code class="language-plaintext highlighter-rouge">server.proto</code>’s <code class="language-plaintext highlighter-rouge">option go_package</code> with <code class="language-plaintext highlighter-rouge">--go_opt=M</code>. That’s because different generated versions of <code class="language-plaintext highlighter-rouge">import.proto</code> may not exist (directly or transitively) in any Go import graph due to Go’s global proto registry. So, <code class="language-plaintext highlighter-rouge">internal/</code> is used to prevent anybody from depending on these <code class="language-plaintext highlighter-rouge">.pb.go</code>s. Read more on this in “Storing generated code”.</p>

<h3 id="large-dependency-graphs">Large dependency graphs</h3>

<p>Using <code class="language-plaintext highlighter-rouge">protoc</code> becomes progressively more unwieldy the larger your proto dependency graph becomes, since you have to specify all transitive dependencies at <code class="language-plaintext highlighter-rouge">protoc</code> time.</p>

<p>Let’s now look at a better tool for managing larger dependency graphs.</p>

<h2 id="generating-go-code-with-buf">Generating Go code with buf</h2>

<p>Generating code with <code class="language-plaintext highlighter-rouge">buf</code> is better for protos that have more than a few dependencies.</p>

<h3 id="simple-code-generation-1">Simple code generation</h3>

<p>Imagine two simple protos:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// protos/user/user.proto
syntax <span class="o">=</span> <span class="s2">"proto3"</span><span class="p">;</span>

package user<span class="p">;</span>

option go_package <span class="o">=</span> <span class="s2">"github.com/username/myrepo/protos/user"</span><span class="p">;</span>

message User <span class="o">{</span>
    string name <span class="o">=</span> 1<span class="p">;</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// protos/server/server.proto
syntax <span class="o">=</span> <span class="s2">"proto3"</span><span class="p">;</span>

package server<span class="p">;</span>

option go_package <span class="o">=</span> <span class="s2">"github.com/username/myrepo/protos/server"</span><span class="p">;</span>

import <span class="s2">"user/user.proto"</span><span class="p">;</span>

message Server <span class="o">{</span>
    repeated user.User <span class="nb">users</span> <span class="o">=</span> 1<span class="p">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Generate Go code (.pb.gos) from these protos with buf by adding:</p>

<ul>
  <li>
    <p>A <code class="language-plaintext highlighter-rouge">buf.yaml</code>:</p>

    <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c1"># buf.yaml</span>
  <span class="c1"># For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">v2</span>
  <span class="na">lint</span><span class="pi">:</span>
    <span class="na">use</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">STANDARD</span>
  <span class="na">breaking</span><span class="pi">:</span>
    <span class="na">use</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">FILE</span>
  <span class="na">modules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">protos/</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>A <code class="language-plaintext highlighter-rouge">buf.gen.yaml</code>:</p>

    <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c1"># buf.gen.yaml</span>
  <span class="c1"># For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-gen-yaml</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">v2</span>
  <span class="na">plugins</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">remote</span><span class="pi">:</span> <span class="s">buf.build/protocolbuffers/go:v1.36.6</span>
      <span class="na">out</span><span class="pi">:</span> <span class="s">protos</span>
      <span class="na">opt</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">paths=source_relative</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>Generate code with <code class="language-plaintext highlighter-rouge">buf generate</code>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>buf generate
<span class="nv">$ </span>tree
<span class="nb">.</span>
|____protos
| |____server
| | |____server.pb.go
| | |____server.proto
| |____user
| | |____user.pb.go
| | |____user.proto
</code></pre></div></div>

<p>Warning: This approach allows others to depend on your generated Go code.</p>

<p>This example signs you up to maintaining generated Go code at <code class="language-plaintext highlighter-rouge">github.com/username/myrepo/protos/server</code> and <code class="language-plaintext highlighter-rouge">[...]/protos/user</code>. Whether or not this is a good idea is discussed in “Storing generated code”. For now suffice it to say that instead of declaring <code class="language-plaintext highlighter-rouge">option go_package</code> you could instead provide <code class="language-plaintext highlighter-rouge">--go_opt=M&lt;proto&gt;=&lt;go import&gt;</code>. And, instead of generating your <code class="language-plaintext highlighter-rouge">.pb.go</code>s into the publicly import-able <code class="language-plaintext highlighter-rouge">protos/</code>, you could generate them into an <code class="language-plaintext highlighter-rouge">internal/</code> directory which is not publicly import-able.</p>

<p>If you need to produce gRPC language bindings, add the gRPC plugin to <code class="language-plaintext highlighter-rouge">buf.gen.yaml</code>:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># buf.gen.yaml</span>
<span class="na">version</span><span class="pi">:</span> <span class="s">v2</span>
<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">remote</span><span class="pi">:</span> <span class="s">buf.build/protocolbuffers/go:v1.36.6</span>
    <span class="na">out</span><span class="pi">:</span> <span class="s">protos</span>
    <span class="na">opt</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">paths=source_relative</span>
  <span class="pi">-</span> <span class="na">remote</span><span class="pi">:</span> <span class="s">buf.build/grpc/go:v1.5.1</span>
    <span class="na">out</span><span class="pi">:</span> <span class="s">protos</span>
    <span class="na">opt</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">paths=source_relative</span>
</code></pre></div></div>

<h3 id="foreign-protos-1">Foreign protos</h3>

<p>Part of your dependency graph may include protos in other repos:</p>

<div class="language-proto highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// protos/user/user.proto</span>
<span class="na">syntax</span> <span class="o">=</span> <span class="s">"proto3"</span><span class="p">;</span>

<span class="kn">package</span> <span class="nn">user</span><span class="p">;</span>

<span class="k">option</span> <span class="na">go_package</span> <span class="o">=</span> <span class="s">"github.com/username/myrepo/protos/user"</span><span class="p">;</span>

<span class="c1">// Not in this repo. Also not repo-rooted, which we'll discuss below. We could</span>
<span class="c1">// change this if we owned user.proto, but if this occurred in a foreign proto</span>
<span class="c1">// that we can't change then we'd have to work around it.</span>
<span class="k">import</span> <span class="s">"non/repo/rooted/import.proto"</span><span class="p">;</span>

<span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span>
    <span class="kt">string</span> <span class="na">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Foreign protos that are neither in your repo nor the Buf Schema Registry (BSR) can be imported, but in a roundabout way. You’ll need to git clone them yourself and include them as modules in buf.yaml.</p>

<p>Before we do so, let’s quickly recap proto quirks you might run into, described above in “Generating code with protoc: Foreign protos”. We’ll need to contend with the fact that the <code class="language-plaintext highlighter-rouge">import.proto</code> import was not rooted at its repo root, and that <code class="language-plaintext highlighter-rouge">import.proto</code> does not define <code class="language-plaintext highlighter-rouge">option go_package</code>.</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># buf.yaml</span>
<span class="na">version</span><span class="pi">:</span> <span class="s">v2</span>
<span class="na">lint</span><span class="pi">:</span>
  <span class="na">use</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">STANDARD</span>
<span class="na">breaking</span><span class="pi">:</span>
  <span class="na">use</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">FILE</span>
<span class="na">modules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">protos/</span>
  <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">tmp/path/to/expected/import/root</span>
</code></pre></div></div>

<p>The non-repo rooted import is handled by specifying the path at which the <code class="language-plaintext highlighter-rouge">import "non/repo/rooted/...";</code> import works.</p>

<p>To handle the lack of <code class="language-plaintext highlighter-rouge">option go_package</code>, we’ll need to turn on managed mode in <code class="language-plaintext highlighter-rouge">buf.gen.yaml</code>:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># buf.gen.yaml</span>
<span class="na">version</span><span class="pi">:</span> <span class="s">v2</span>
<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">remote</span><span class="pi">:</span> <span class="s">buf.build/protocolbuffers/go:v1.36.6</span>
    <span class="na">out</span><span class="pi">:</span> <span class="s">internal/protogen</span>
    <span class="na">opt</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">paths=source_relative</span>
<span class="na">managed</span><span class="pi">:</span>
  <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
  <span class="na">override</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">file_option</span><span class="pi">:</span> <span class="s">go_package_prefix</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">non/repo/rooted/</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">github.com/foreignorg/foreignrepo/path/to/expected/import/root</span>
    <span class="pi">-</span> <span class="na">file_option</span><span class="pi">:</span> <span class="s">go_package_prefix</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">user/user.proto</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">github.com/username/myrepo/internal/protogen</span>
    <span class="pi">-</span> <span class="na">file_option</span><span class="pi">:</span> <span class="s">go_package_prefix</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">server/server.proto</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">github.com/username/myrepo/internal/protogen</span>
</code></pre></div></div>

<p>Next, let’s perform the <code class="language-plaintext highlighter-rouge">git clone</code> and <code class="language-plaintext highlighter-rouge">tmp/</code> management:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/foreignorg/foreignrepo.git tmp/foreign
buf generate
</code></pre></div></div>

<p>Which when run generates:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>tree
<span class="nb">.</span>
|____internal
| |____protogen
| | |____server
| | | |____server.pb.go
| | |____user
| | | |____user.pb.go
| | |____non
| | | |____repo
| | | | |____rooted
| | | | | |____import.pb.go
</code></pre></div></div>

<p>Note that all the generated code was generated into <code class="language-plaintext highlighter-rouge">internal/</code>. That involved overwriting <code class="language-plaintext highlighter-rouge">user.proto</code> and <code class="language-plaintext highlighter-rouge">server.proto</code>’s <code class="language-plaintext highlighter-rouge">option go_package</code> with <code class="language-plaintext highlighter-rouge">--go_opt=M</code>. That’s because different generated versions of <code class="language-plaintext highlighter-rouge">import.proto</code> may not exist (directly or transitively) in any Go import graph due to Go’s global proto registry. So, <code class="language-plaintext highlighter-rouge">internal/</code> is used to prevent anybody from depending on these <code class="language-plaintext highlighter-rouge">.pb.go</code>s. Read more on this in “Storing generated code”.</p>

<h3 id="concurrent-git-clones">Concurrent git clones</h3>

<p>If you have many <code class="language-plaintext highlighter-rouge">git clone</code> statements, consider using <code class="language-plaintext highlighter-rouge">&amp;</code> and <code class="language-plaintext highlighter-rouge">wait</code> to run them concurrently:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone ... &amp;
git clone ... &amp;
git clone ... &amp;
<span class="nb">wait
</span>buf generate
</code></pre></div></div>]]></content><author><name></name></author><category term="protobuf" /><summary type="html"><![CDATA[Generating Go code from protobufs without the buf BSR.]]></summary></entry></feed>