<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Mitchell Hashimoto</title>
  <subtitle>Browse aymeric&#39;s, an independent, searchable archive of readable snapshots from followed feeds, preserved in Git with aggr.</subtitle>
  <id>https://aggr.aymericbeaumet.com/sources/mitchellh.com/</id>
  <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/sources/mitchellh.com/"/>
  <link rel="self" type="application/atom+xml" href="https://aggr.aymericbeaumet.com/sources/mitchellh.com/atom.xml"/>
  <updated>2026-10-06T00:00:00+00:00</updated>
  <author>
    <name>aymeric&#39;s</name>
  </author>
  <generator version="2.0.0" uri="https://github.com/aymericbeaumet/aggr">aggr</generator>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:0a775d92bd5862225a128d760242c3a960021362</id>
    <title type="text">A Terminal Protocol for Program Status (OSC 7501)</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-10-06-a-terminal-protocol-for-program-status-osc-7501/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/program-status-osc7501"/>
    <published>2026-10-06T00:00:00+00:00</published>
    <updated>2026-10-06T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">I wrote a specification for a new terminal escape sequence: OSC 7501, the Program Status Protocol. It lets any program tell the terminal what it&#39;s doing: idle, working, waiting on the user, finished, or failed, and why. For example, here…</summary>
    <content type="html">&lt;p&gt;I wrote a specification for a new terminal escape sequence: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status&quot;&gt;OSC 7501, the Program Status Protocol&lt;/a&gt;. It lets any program tell the terminal what it&#39;s doing: idle, working, waiting on the user, finished, or failed, and why.&lt;/p&gt;
&lt;p&gt;For example, here is how &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.hashicorp.com/terraform&quot;&gt;Terraform&lt;/a&gt; could indicate that it is blocked waiting for user input, with the message &amp;quot;Apply 3 to add, 1 to change, 0 to destroy?&amp;quot; (base64-encoded). A terminal (or any other tool running Terraform) could show this information however it feels appropriate: a notification, an inbox, a status icon, etc.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;ESC ] 7501 ; state=blocked:kind=permission:app=terraform:msg=QXBwbHkgMyB0byBhZGQsIDEgdG8gY2hhbmdlLCAwIHRvIGRlc3Ryb3k/ ESC \
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This post covers why I think this protocol needs to exist, why the existing approaches aren&#39;t good enough (especially for coding agents), and how the protocol works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a completely generic, terminal-native specification and protocol.&lt;/strong&gt; It emerged from my work on &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical&quot;&gt;Superlogical&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/&quot;&gt;Ghostty&lt;/a&gt; but &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status&quot;&gt;the specification&lt;/a&gt; has no product-specific functionality or language. It is designed as an idiomatic, well-formed specification that any terminal developer will find familiar.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Long-running work is common in terminals: builds, deployments, package upgrades, data processing, and, more and more today, coding agents. These programs alternate between working on their own, waiting on the user, and finishing. Meanwhile, users usually go off and do something else and want to know when the work finishes or needs them.&lt;/p&gt;
&lt;p&gt;Aspects of this problem have been solved in various ways going back decades. For example, some terminals monitor the active foreground process and have features to notify when it changes. Or, they wait for some time period of &amp;quot;quiet&amp;quot; (for various definitions) output. The specification also &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#relationship-to-other-sequences&quot;&gt;lists the reasons why existing sequences aren&#39;t enough&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ultimately, I felt there wasn&#39;t a cohesive, interaction-agnostic, generic solution to this problem that conveyed progress, blocking, completion, and trees of tasks. And it wasn&#39;t possible to cobble together pre-existing sequences to achieve it robustly, either.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Singling Out &amp;quot;Agentic Inboxes&amp;quot;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Don&#39;t care about AI, LLMs, etc.? Skip this section.&lt;/strong&gt; The problem is generic and applies in a compelling way without bringing in AI. It&#39;s particularly nasty with AI so I want to call it out, but if you don&#39;t care about any of that, just skip this.&lt;/p&gt;
&lt;p&gt;It&#39;s now increasingly common for people to run many long-running agents for any number of reasons: background research, issue monitoring, bug fixing, large features, etc. Each one works for a while and then stops to ask for permission, ask a question, or report it&#39;s done.&lt;/p&gt;
&lt;p&gt;From this, a new category of tool has emerged that I&#39;ll simply call the &lt;em&gt;agentic inbox&lt;/em&gt;: a single view across every running agent showing which are working, which are done, and which are waiting on you. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://herdr.dev/&quot;&gt;Herdr&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://cmux.com/&quot;&gt;cmux&lt;/a&gt;, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/asheshgoplani/agent-deck&quot;&gt;Agent Deck&lt;/a&gt; are a few examples of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/andyrewlee/awesome-agent-orchestrators&quot;&gt;hundreds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Without a dedicated protocol, they solve the agent status problem in two ways: heuristics and non-terminal APIs.&lt;/p&gt;
&lt;h3&gt;Heuristics&lt;/h3&gt;
&lt;p&gt;The first approach is to guess by reading the screen or the window title and matching it against known patterns.&lt;/p&gt;
&lt;p&gt;Herdr is a good example because it does this well and documents it openly. Its &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://herdr.dev/docs/agents/#detection-manifests&quot;&gt;detection manifests&lt;/a&gt; are TOML rules that classify an agent as idle, working, or blocked. Here is &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/herdrdev/herdr/blob/3d9d2b18dab139ba226ebc5a1c9a9f2c9c3ee4df/src/detect/manifests/claude.toml#L7-L14&quot;&gt;the first of 16 rules&lt;/a&gt; for Claude Code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;TOML&quot;&gt;[[rules]]
id = &amp;quot;osc_title_working&amp;quot;
state = &amp;quot;working&amp;quot;
priority = 1100
region = &amp;quot;osc_title&amp;quot;
visible_working = true
# Braille covers &amp;lt;= 2.1.227; half-circles are the 2.1.228 busy spinner.
regex = [&amp;#39;^[\x{2800}-\x{28FF}\x{25D0}-\x{25D3}] &amp;#39;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude Code is considered &amp;quot;working&amp;quot; if its window title starts with a Braille spinner character or, as of version 2.1.228, a half-circle one. The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/herdrdev/herdr/commits/master/src/detect/manifests/claude.toml&quot;&gt;history of that file&lt;/a&gt; shows ten changes in three months for just Claude Code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This isn&#39;t a criticism of Herdr.&lt;/strong&gt; Its maintainers are doing the best possible job with the tools available. But it demonstrates well the benefit &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status&quot;&gt;a unified protocol&lt;/a&gt; would have.&lt;/p&gt;
&lt;h3&gt;Non-Terminal APIs&lt;/h3&gt;
&lt;p&gt;The second approach is to have the program report its own state through an inbox-specific, out-of-band API such as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://herdr.dev/docs/socket-api/#agent-state-reporting&quot;&gt;Herdr&#39;s socket API&lt;/a&gt; or &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://cmux.com/docs/notifications&quot;&gt;&lt;code&gt;cmux notify&lt;/code&gt;&lt;/a&gt;. This is better than heuristics in some ways, because the program that actually knows its state is the one reporting it. But every program has to integrate with every inbox separately, and a local socket doesn&#39;t work over SSH or from within a container without extra bridging. The pty already works across all of this.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Program Status Protocol&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status&quot;&gt;OSC 7501&lt;/a&gt; is a terminal native answer to this problem. The program reports its own state directly via the pty that it always has, using a format that is safe to send everywhere (well-behaved terminals ignore unknown OSCs).&lt;/p&gt;
&lt;p&gt;The body of the sequence is a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#syntax&quot;&gt;list of &lt;code&gt;key=value&lt;/code&gt; pairs&lt;/a&gt; separated by &lt;code&gt;:&lt;/code&gt;. The only required key is &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#states&quot;&gt;&lt;code&gt;state&lt;/code&gt;&lt;/a&gt;, which is one of:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;state&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At rest, waiting for the user&#39;s next instruction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;working&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Running. May include a &lt;code&gt;progress&lt;/code&gt; percentage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Finished. The result is ready and the user hasn&#39;t seen it yet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;blocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can&#39;t continue until the user does something. &lt;code&gt;kind&lt;/code&gt; says what (&lt;code&gt;permission&lt;/code&gt;, &lt;code&gt;question&lt;/code&gt;, or &lt;code&gt;auth&lt;/code&gt;) and &lt;code&gt;msg&lt;/code&gt; says why.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Failed and stopped.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#keys&quot;&gt;Optional keys&lt;/a&gt; include &lt;code&gt;app&lt;/code&gt;, a stable machine-readable program name like &lt;code&gt;cargo&lt;/code&gt; or &lt;code&gt;claude-code&lt;/code&gt;, and &lt;code&gt;msg&lt;/code&gt;, a single human-readable line encoded as base64.&lt;/p&gt;
&lt;p&gt;Programs that run several things at once can report multiple records using &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#records-and-ids&quot;&gt;hierarchical ids&lt;/a&gt;. A &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#several-records&quot;&gt;deploy tool&lt;/a&gt; can be &lt;code&gt;working&lt;/code&gt; at the root while &lt;code&gt;us-east&lt;/code&gt; pushes an image at 40% and &lt;code&gt;eu-west&lt;/code&gt; is &lt;code&gt;blocked&lt;/code&gt; waiting for approval to deploy to production. Both are true at the same time, and the terminal decides what to show. A &lt;code&gt;clear&lt;/code&gt; state removes records.&lt;/p&gt;
&lt;p&gt;Here&#39;s a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#a-shell-function&quot;&gt;complete integration&lt;/a&gt; for a shell script to wrap &lt;code&gt;rsync&lt;/code&gt; to participate in this protocol:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;status() {
  printf &amp;#39;\e]7501;state=%s:msg=%s\e\\&amp;#39; &amp;quot;$1&amp;quot; &amp;quot;$(printf &amp;#39;%s&amp;#39; &amp;quot;$2&amp;quot; | base64 | tr -d &amp;#39;\n&amp;#39;)&amp;quot;
}
status working &amp;quot;Syncing photos&amp;quot;
rsync -a ~/Photos backup:/photos &amp;amp;&amp;amp; status done &amp;quot;Photos synced&amp;quot; || status error &amp;quot;rsync failed&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trivial to script with plain old POSIX &lt;code&gt;sh&lt;/code&gt;. No SDK, no sockets, no environment variables, no JSON. No bias towards specific GUI presentations. No bias to any specific workload (like AI). A well-formed, generic foundation to build functionality above that anyone and everyone can participate with.&lt;/p&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status&quot;&gt;full spec&lt;/a&gt; covers the rest: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#states&quot;&gt;record lifetime&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#feature-detection&quot;&gt;feature detection&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#terminfo&quot;&gt;terminfo&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#limits&quot;&gt;size limits&lt;/a&gt;, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status#security&quot;&gt;security&lt;/a&gt;. It&#39;s short. I wrote it all by hand. Please read it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Implement It&lt;/h2&gt;
&lt;p&gt;I wrote &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex/docs/build/program-status&quot;&gt;this specification&lt;/a&gt; based on my experience maintaining a terminal emulator for many years now. It is written in a way that is easy for application developers to emit, and easy for terminal emulators to consume and parse.&lt;/p&gt;
&lt;p&gt;I&#39;ve already implemented this protocol twice. We have one implementation in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/14560&quot;&gt;libghostty&lt;/a&gt; and I have a parallel implementation in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/rex&quot;&gt;Rex&lt;/a&gt;. I&#39;ve also implemented it as a proof-of-concept in Terraform, Claude Code, Codex, and Homebrew via either plugins or forks. In each case, the implementation was no more than a dozen lines.&lt;/p&gt;
&lt;p&gt;I&#39;ve been in contact with the maintainers of many popular terminal programs and emulators and they&#39;ve helped review and shape the specification. But if you have any more feedback, I&#39;m happy to hear it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you&#39;ve implemented the specification&lt;/strong&gt; please let me know via email (mail icon in the footer) and I&#39;ll add you to the list of tools that implement this. Thank you.&lt;/p&gt;
&lt;p&gt;I&#39;d like us all to stop guessing what programs are doing by reading their screens or process trees. The program already knows. Let&#39;s give it a way to tell us!&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:dccab705f0bf8648f12e6201d1e582176788b7cc</id>
    <title type="text">Superlogical</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-07-29-superlogical/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/superlogical"/>
    <published>2026-07-29T00:00:00+00:00</published>
    <updated>2026-07-29T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">I&#39;ve started a new company: Superlogical. I authored 1 the announcement post on the Superlogical homepage, which describes the problem statement and what we&#39;re building.2 Read that to learn more. This post is more of a personal statement…</summary>
    <content type="html">&lt;p&gt;I&#39;ve started a new company: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/&quot;&gt;Superlogical&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I authored &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fn-1&quot;&gt;1&lt;/a&gt; the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/&quot;&gt;announcement post on the Superlogical homepage&lt;/a&gt;, which describes the problem statement and what we&#39;re building.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fn-4&quot;&gt;2&lt;/a&gt; Read that to learn more. This post is more of a personal statement about what led to this, how it relates to my recent work, and what comes next.&lt;/p&gt;
&lt;h2&gt;Building for the Love of It&lt;/h2&gt;
&lt;p&gt;In 2023, after 11 years, I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.hashicorp.com/en/blog/mitchell-reflects-as-he-departs-hashicorp&quot;&gt;left the company I previously founded&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since then, my primary focus has been being a mostly full-time dad. I became involved with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-donation-2026&quot;&gt;a lot&lt;/a&gt; of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://endowment.dev/&quot;&gt;philanthropy&lt;/a&gt;.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fn-2&quot;&gt;3&lt;/a&gt; I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing&quot;&gt;wrote a lot&lt;/a&gt; on technical topics, sharing as much as I had the time and energy for.&lt;/p&gt;
&lt;p&gt;But I never stopped building. Building software is my personal passion, and I continued to learn and build a little every day just for the love of it.&lt;/p&gt;
&lt;h2&gt;Ghostty&lt;/h2&gt;
&lt;p&gt;I poured most of that passion into &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/ghostty&quot;&gt;Ghostty&lt;/a&gt;. Ghostty started as a personal project I thought I&#39;d throw away &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fn-3&quot;&gt;4&lt;/a&gt;, but instead grew into a project with millions of daily users, a large community with a wonderful group of core maintainers, and a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://libghostty.tip.ghostty.org/&quot;&gt;reusable library&lt;/a&gt; powering &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/vercel/turborepo&quot;&gt;impactful&lt;/a&gt; &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/antithesishq/bombadil&quot;&gt;software&lt;/a&gt; as well as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://x.com/teej_dv/status/2081310218617528624&quot;&gt;fun software&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have no intention of ever commercializing Ghostty. To solidify this, I donated the entirety of Ghostty to a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-non-profit&quot;&gt;federally recognized non-profit&lt;/a&gt; in 2025. This ownership legally binds Ghostty to the public-benefit purpose it was created to serve. It is now and forever an open building block that everyone can build on and learn from.&lt;/p&gt;
&lt;p&gt;Ghostty is much more than just me, I don&#39;t want to give the wrong impression! Ghostty is made up of about a dozen core maintainers, almost 50 localization team members, and another dozen community moderators. Some of these people are &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/sponsor#contributor-compensation&quot;&gt;paid via the non-profit&lt;/a&gt;. It is a very healthy open source project made up of a diverse group of talented individuals.&lt;/p&gt;
&lt;p&gt;I have had, and continue to have, an immense amount of fun working on Ghostty. And over my years working on it, I became increasingly interested in the problems &lt;em&gt;around&lt;/em&gt; the terminal emulator itself. Or, to be purposefully coy, I kept asking “why?” Why is terminal usage more widespread than ever? What continues to drive its growth? At what point does that end? What comes next? Why? Why? Why!&lt;/p&gt;
&lt;h2&gt;What Comes Next&lt;/h2&gt;
&lt;p&gt;The answer to these questions leads to Superlogical.&lt;/p&gt;
&lt;p&gt;I wasn&#39;t searching for a reason to start another company, but I found a problem I wanted to spend years working on, a scope that required a team, and people I wanted to build it with.&lt;/p&gt;
&lt;p&gt;Superlogical will begin by shipping a terminal multiplexer. I&#39;m pouring my years of experience building a terminal and studying the potential and limitations of other multiplexers into something new, powerful, and, of course, fast.&lt;/p&gt;
&lt;p&gt;My cofounders are some of the most talented people I&#39;ve ever worked with. They&#39;re amazingly accomplished and their work speaks for itself. They&#39;re each going to play an equal part in making this something really special. One thing we all share is an uncompromising commitment to well-crafted software and software experiences.&lt;/p&gt;
&lt;p&gt;We will build on libghostty exactly as it was designed to be used: as a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy&quot;&gt;public building block&lt;/a&gt; for terminal applications. Superlogical will consume the same MIT-licensed components available to everyone else, and we will continue to upstream shared terminal work so &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/Uzaaft/awesome-libghostty&quot;&gt;every libghostty consumer can benefit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ghostty itself remains a non-profit; its mission, governance, license, technical goals, and roadmap do not change.&lt;/p&gt;
&lt;p&gt;Of course, the terminal multiplexer isn&#39;t &lt;em&gt;the entire vision&lt;/em&gt;. I&#39;m not ready to share more yet, but you can learn a tiny bit more &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/&quot;&gt;on the Superlogical website&lt;/a&gt;. Sign up for the newsletter for beta access and development updates along the way.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fn-5&quot;&gt;5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Oh, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.superlogical.com/jobs&quot;&gt;we&#39;re hiring!&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;No AI! Hand-written, real, and authentic. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I realize there is some vaguery and unanswered questions. We have a lot more answers but don&#39;t want to just lay it all out in a tome on a landing page. We&#39;ll reveal more through updates over time. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My family doesn&#39;t publicize most of our philanthropic work. A large majority is non-tech related. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The kind you use to learn something but never publish. We all have dozens (hundreds?) of those in a GitHub graveyard. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We considered very strongly not sharing anything at all until we were ready to ship the product, but devlogs are fun, building in public is fun, and I think this team has proven we can and will ship. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/superlogical#user-content-fnref-5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:e15c21825d0d9d17a052618336a49a7532d718b8</id>
    <title type="text">Everyone Should Know SIMD</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-07-22-everyone-should-know-simd/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/everyone-should-know-simd"/>
    <published>2026-07-22T00:00:00+00:00</published>
    <updated>2026-07-22T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">SIMD has a reputation for being complex. I&#39;ve met many very good software engineers who dismiss it as something too complex to learn or a niche optimization meant for only the highest-performance software, not useful in everyday…</summary>
    <content type="html">&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/Single_instruction,_multiple_data&quot;&gt;SIMD&lt;/a&gt; has a reputation for being complex. I&#39;ve met many very good software engineers who dismiss it as something too complex to learn or a niche optimization meant for only the highest-performance software, not useful in everyday programming.&lt;/p&gt;
&lt;p&gt;I think that&#39;s wrong. SIMD can be simple to understand &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fn-1&quot;&gt;1&lt;/a&gt;, and common &amp;quot;process N values at a time&amp;quot; SIMD code to speed up a naive for loop almost always follows the same general shape. Once you learn the basics, writing SIMD is just about as easy as a for loop. And when it&#39;s not, it&#39;s usually a good sign to skip it for now.&lt;/p&gt;
&lt;p&gt;Every developer should know at least that much SIMD.&lt;/p&gt;
&lt;p&gt;This post uses Zig for examples but is a general piece that applies to any programming language. Support for SIMD instructions varies by programming language and I hope that more programming languages expose these generic concepts in the future!&lt;/p&gt;
&lt;p&gt;I hate that I have to do this for every post now, but I also want to note this was completely hand-written with no AI assistance.&lt;/p&gt;
&lt;p&gt;Table of Contents&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#background-what-is-simd&quot;&gt;Background: What Is SIMD?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#the-common-shape&quot;&gt;The Common Shape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#a-real-example&quot;&gt;A Real Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#step-1-broadcast-constants&quot;&gt;Step 1: Broadcast Constants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#step-2-loop-one-vector-at-a-time&quot;&gt;Step 2: Loop One Vector at a Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#step-3-perform-the-simd-operation&quot;&gt;Step 3: Perform the SIMD Operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#step-4-reduce-the-vector-result&quot;&gt;Step 4: Reduce the Vector Result&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#step-5-finish-with-the-scalar-tail&quot;&gt;Step 5: Finish with the Scalar Tail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#recap-the-common-shape&quot;&gt;Recap: The Common Shape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#why-cant-the-compiler-do-this&quot;&gt;Why Can&#39;t the Compiler Do This?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#everyone-should-know-simd&quot;&gt;Everyone Should Know SIMD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Background: What Is SIMD?&lt;/h2&gt;
&lt;p&gt;If you already know what SIMD is, skip this section.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/Single_instruction,_multiple_data&quot;&gt;SIMD&lt;/a&gt; allows a CPU to operate on multiple values in parallel. For example, instead of comparing one byte at a time, a CPU can compare 4, 8, or even more bytes with a single instruction.&lt;/p&gt;
&lt;p&gt;If you ever see loops like this in your code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;for (byte in bytes) { /* ... */ }
for (character in string) { /* ... */ }
for (value in array) { /* ... */ }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is an opportunity to use SIMD. SIMD turns those into this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;for (8 byte chunk in bytes) { /* ... */ }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This results in a localized speedup that directly maps to the parallelism: you process data 4x, 8x, or even faster.&lt;/p&gt;
&lt;p&gt;The only real requirement for this to pay off is that you need to be regularly processing a large enough number of bytes. If you&#39;re doing these for loops across data that is only ever a handful or dozens of bytes, it&#39;s not worth it. But if this is iterating over hundreds, thousands, millions of bytes, the payoff will be huge.&lt;/p&gt;
&lt;p&gt;That&#39;s the basics. Projects such as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/simdutf/simdutf&quot;&gt;simdutf&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/simdjson/simdjson&quot;&gt;simdjson&lt;/a&gt; take this to an extreme and use SIMD techniques that can be difficult to understand. But you do not need to write algorithms like those to benefit from SIMD. The common case is dramatically simpler.&lt;/p&gt;
&lt;h2&gt;The Common Shape&lt;/h2&gt;
&lt;p&gt;The common &amp;quot;process N values at a time&amp;quot; SIMD code follows the same five steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Broadcast any constants you need and initialize vector accumulators, if any.&lt;/li&gt;
&lt;li&gt;Loop over input one vector-width chunk at a time.&lt;/li&gt;
&lt;li&gt;Perform the comparison or arithmetic across all lanes in parallel.&lt;/li&gt;
&lt;li&gt;Reduce or store the vector result as needed.&lt;/li&gt;
&lt;li&gt;Handle the remaining elements with a scalar tail. A scalar tail is just your &lt;em&gt;normal&lt;/em&gt; loop from before vectorizing, but it only processes the remainder that doesn&#39;t fit into a full vector.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As you do this more and more, you&#39;ll begin to naturally decompose every for loop into these five steps and writing SIMD becomes nearly as natural as writing a scalar loop.&lt;/p&gt;
&lt;h2&gt;A Real Example&lt;/h2&gt;
&lt;p&gt;Let&#39;s look at a real example from Ghostty. We&#39;ll look at the scalar implementation, the SIMD implementation, and then map it back to the common shape above.&lt;/p&gt;
&lt;p&gt;I have a slice of decoded codepoints that I want to consume until I see a value at or below &lt;code&gt;0xF&lt;/code&gt; (a C0 control character).&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fn-3&quot;&gt;2&lt;/a&gt; Terminals are &lt;em&gt;mostly&lt;/em&gt; plain characters to be printed, so we try to batch all those together. So this loop finds the end of the next printable run as quickly as possible.&lt;/p&gt;
&lt;p&gt;The scalar loop is one line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;while (end &amp;lt; cps.len and cps[end] &amp;gt; 0xF) end += 1;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It processes one codepoint at a time. It is easy to understand.&lt;/p&gt;
&lt;p&gt;Here is the generic vector version with no CPU-specific intrinsics &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fn-4&quot;&gt;3&lt;/a&gt; and no comments. I will explain it in detail later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;JavaScript&quot;&gt;&lt;span class=&quot;syntax-source syntax-js&quot;&gt;&lt;span class=&quot;syntax-meta syntax-conditional syntax-js&quot;&gt;&lt;span class=&quot;syntax-keyword syntax-control syntax-conditional syntax-js&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;simd&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;u32&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-bitwise syntax-js&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-bitwise syntax-js&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-object-literal syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;{&lt;/span&gt;
    const V = @&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;Vector&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-parameter syntax-function syntax-js&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;;
    const &lt;span class=&quot;syntax-meta syntax-object-literal syntax-key syntax-js&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-key-value syntax-js&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-constant syntax-js&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;splat&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;xF&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;;
    &lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt; &lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;end&lt;/span&gt; + &lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;lanes&lt;/span&gt; &amp;lt;&lt;span class=&quot;syntax-meta syntax-parameter syntax-optional syntax-js&quot;&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;cps&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property syntax-object syntax-js&quot;&gt;len&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-key-value syntax-js&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-augmented syntax-js&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt; {
        const &lt;span class=&quot;syntax-meta syntax-object-literal syntax-key syntax-js&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-key-value syntax-js&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-constant syntax-js&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;cps&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-brackets syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-brackets syntax-js&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-brackets syntax-js&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-brackets syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-brackets syntax-js&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-constant syntax-numeric syntax-js&quot;&gt;0.&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property syntax-object syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-brackets syntax-js&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-arithmetic syntax-js&quot;&gt;*&lt;/span&gt;;
        const greater_than_threshold = values &amp;gt; threshold;
        &lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt; &lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;@&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;reduce&lt;/span&gt;(.&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;And&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-parameter syntax-function syntax-js&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;greater_than_threshold&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;) continue;
        const &lt;span class=&quot;syntax-meta syntax-object-literal syntax-key syntax-js&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-key-value syntax-js&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property syntax-object syntax-js&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property syntax-object syntax-js&quot;&gt;unsigned&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-comma syntax-js&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;bitCast&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;greater_than_threshold&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;;
        end += @&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;ctz&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;~&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;;
        break;
    &lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
}

&lt;span class=&quot;syntax-meta syntax-while syntax-js&quot;&gt;&lt;span class=&quot;syntax-keyword syntax-control syntax-loop syntax-js&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-relational syntax-js&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;cps&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property syntax-object syntax-js&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;cps&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-brackets syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-brackets syntax-js&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-brackets syntax-js&quot;&gt;]&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-relational syntax-js&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;syntax-constant syntax-numeric syntax-js&quot;&gt;0xF&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-augmented syntax-js&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;syntax-constant syntax-numeric syntax-js&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;12 more lines of code.&lt;/p&gt;
&lt;p&gt;This can improve the loop&#39;s throughput by up to 4x with ARM NEON (including Apple Silicon), 8x with AVX2 (most modern x86 CPUs), and 16x with AVX-512 (some Intel CPUs and AMD Zen 4 and newer).&lt;/p&gt;
&lt;p&gt;In real-world end-to-end throughput from terminal program to finalized terminal state on an AVX2 Intel desktop, this was more like a 5x speedup. You always lose some of the ideal speedup due to the other stuff around the SIMD code, but... that&#39;s still 5x!&lt;/p&gt;
&lt;p&gt;Okay, now I understand that those 12 lines are going to look really alien to someone not familiar with the concepts. So now let&#39;s back up and explain it step by step, mapping it directly to the shape previously mentioned.&lt;/p&gt;
&lt;h2&gt;Step 1: Broadcast Constants&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with the first three lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;JavaScript&quot;&gt;&lt;span class=&quot;syntax-source syntax-js&quot;&gt;&lt;span class=&quot;syntax-meta syntax-conditional syntax-js&quot;&gt;&lt;span class=&quot;syntax-keyword syntax-control syntax-conditional syntax-js&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;simd&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;u32&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-bitwise syntax-js&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-bitwise syntax-js&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-object-literal syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;{&lt;/span&gt;
    const V = @&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;Vector&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-parameter syntax-function syntax-js&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;;
    const &lt;span class=&quot;syntax-meta syntax-object-literal syntax-key syntax-js&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-key-value syntax-js&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-constant syntax-js&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;syntax-meta syntax-function syntax-declaration syntax-js&quot;&gt;&lt;span class=&quot;syntax-entity syntax-name syntax-function syntax-js&quot;&gt;splat&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-begin syntax-js&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;syntax-variable syntax-parameter syntax-function syntax-js&quot;&gt;xF&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-end syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;simd.lanes(u32)&lt;/code&gt; is a helper in Ghostty that returns the number of &lt;code&gt;u32&lt;/code&gt; values the target CPU can process at once. These individual values are called &lt;em&gt;lanes&lt;/em&gt;. On ARM this returns 4, AVX2 returns 8, and AVX-512 returns 16. If the target doesn&#39;t have a vector size we want to use, it returns &lt;code&gt;null&lt;/code&gt; and we skip all of this code and do zero SIMD work.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Vector(lanes, u32)&lt;/code&gt; creates the vector type. If &lt;code&gt;lanes&lt;/code&gt; is 8, then &lt;code&gt;V&lt;/code&gt; is a single value containing eight &lt;code&gt;u32&lt;/code&gt; values that the CPU can operate on in parallel. And so on.&lt;/p&gt;
&lt;p&gt;Finally, we need to compare every value to &lt;code&gt;0xF&lt;/code&gt;. A vector comparison requires a vector on both sides, so &lt;code&gt;@splat(0xF)&lt;/code&gt; copies, or &lt;em&gt;broadcasts&lt;/em&gt;, &lt;code&gt;0xF&lt;/code&gt; into every lane. The result is a vector that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;{ 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is step 1: prepare the vector type and broadcast any constants. Some algorithms also initialize a vector accumulator here, but this algorithm doesn&#39;t need one.&lt;/p&gt;
&lt;h2&gt;Step 2: Loop One Vector at a Time&lt;/h2&gt;
&lt;p&gt;Next, we loop over one complete vector at a time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;while (end + lanes &amp;lt;= cps.len) : (end += lanes) {
    const values: V = cps[end..][0..lanes].*;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;lanes&lt;/code&gt; is 8, we only enter the loop when at least eight values remain. Inside the loop, we load those eight values into the vector &lt;code&gt;values&lt;/code&gt;. At the end of every loop, &lt;code&gt;end += lanes&lt;/code&gt; moves forward by eight values instead of one.&lt;/p&gt;
&lt;p&gt;The requirement for a &lt;em&gt;complete&lt;/em&gt; vector is important. If only five values remain, we can&#39;t load an eight-lane vector. There are various tricks to handle this, but we do the easy thing and handle them via our scalar tail, which I&#39;ll explain later in step 5.&lt;/p&gt;
&lt;p&gt;This is step 2: load and loop over the input one vector-width chunk at a time. You can see the lane-count speedup here!&lt;/p&gt;
&lt;h2&gt;Step 3: Perform the SIMD Operation&lt;/h2&gt;
&lt;p&gt;Now we perform the comparison:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;const greater_than_threshold = values &amp;gt; threshold;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both &lt;code&gt;values&lt;/code&gt; and &lt;code&gt;threshold&lt;/code&gt; are vectors, so this maps to a vector operation (a literal vector CPU instruction). The one &lt;code&gt;&amp;gt;&lt;/code&gt; compares every lane in &lt;code&gt;values&lt;/code&gt; to every corresponding lane in &lt;code&gt;threshold&lt;/code&gt;. If there are eight lanes, this is equivalent to performing the scalar comparison &lt;code&gt;cps[end] &amp;gt; 0xF&lt;/code&gt; eight times, but it does it in one CPU instruction instead.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fn-5&quot;&gt;4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The result is another vector with one boolean per lane. Conceptually, it looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;values:                 { 0x41, 0x42, 0x43, 0x0A, 0x44, 0x45, 0x46, 0x47 }
threshold:              {  0xF,  0xF,  0xF,  0xF,  0xF,  0xF,  0xF,  0xF }
greater_than_threshold: { true, true, true, false, true, true, true, true }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the actual SIMD operation. There is no explicit inner loop. The &lt;code&gt;&amp;gt;&lt;/code&gt; operator applies to every lane in parallel.&lt;/p&gt;
&lt;p&gt;Comparisons are only one example. This could be addition, multiplication, minimum, maximum, or any other operation supported by the vector type. The point is the code still has the same shape.&lt;/p&gt;
&lt;h2&gt;Step 4: Reduce the Vector Result&lt;/h2&gt;
&lt;p&gt;We now have a vector of booleans, but the original loop needs to know the location of the first value at or below &lt;code&gt;0xF&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, let&#39;s handle the common case where every value is above &lt;code&gt;0xF&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;if (@reduce(.And, greater_than_threshold)) continue;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@reduce(.And, ...)&lt;/code&gt; combines every boolean using &lt;code&gt;and&lt;/code&gt; and returns a single boolean. If every lane is &lt;code&gt;true&lt;/code&gt;, we &lt;code&gt;continue&lt;/code&gt; and process the next vector. In our example, lane 3 is &lt;code&gt;false&lt;/code&gt;, so &lt;code&gt;@reduce&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; and we fall through to find exactly which lane failed.&lt;/p&gt;
&lt;p&gt;If any lane is &lt;code&gt;false&lt;/code&gt;, then we need to find exactly which lane failed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;const mask: std.meta.Int(.unsigned, lanes) = @bitCast(greater_than_threshold);
end += @ctz(~mask);
break;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@bitCast&lt;/code&gt; turns the vector of booleans into an integer with one bit per lane. A &lt;code&gt;1&lt;/code&gt; bit means the value was greater than &lt;code&gt;0xF&lt;/code&gt; and a &lt;code&gt;0&lt;/code&gt; means it wasn&#39;t. We invert the mask so failed comparisons are &lt;code&gt;1&lt;/code&gt;, and then &lt;code&gt;@ctz&lt;/code&gt; counts the number of zero bits before the first failure. That count is the index of the first failing lane.&lt;/p&gt;
&lt;p&gt;We add that index to &lt;code&gt;end&lt;/code&gt; and break because we found the control character.&lt;/p&gt;
&lt;p&gt;Using the same values from step 3, we can see this transformation per lane:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;values:                 { 0x41, 0x42, 0x43, 0x0A, 0x44, 0x45, 0x46, 0x47 }
greater_than_threshold: { true, true, true, false, true, true, true, true }
mask:                   {    1,    1,    1,     0,    1,    1,    1,    1 }
~mask:                  {    0,    0,    0,     1,    0,    0,    0,    0 }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@ctz(~mask)&lt;/code&gt; counts three zero bits before the first &lt;code&gt;1&lt;/code&gt;, so it returns &lt;code&gt;3&lt;/code&gt;. Adding &lt;code&gt;3&lt;/code&gt; to &lt;code&gt;end&lt;/code&gt; points it at lane 3, which contains &lt;code&gt;0x0A&lt;/code&gt;, the first control character.&lt;/p&gt;
&lt;p&gt;This is step 4: reduce the vector result into whatever the original algorithm needs. This is also the step that varies the most between algorithms. A sum might reduce a vector accumulator into a single number. A transform might store the entire vector to an output buffer. Our scan turns the vector into a bit mask so it can find one specific lane.&lt;/p&gt;
&lt;h2&gt;Step 5: Finish with the Scalar Tail&lt;/h2&gt;
&lt;p&gt;After the vector loop, we run the exact scalar loop we started with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;while (end &amp;lt; cps.len and cps[end] &amp;gt; 0xF) end += 1;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the input length isn&#39;t an exact multiple of the vector width, this processes the remaining values. For example, an eight-lane vector loop leaves anywhere from zero to seven values for this loop. This is called the &lt;em&gt;scalar tail&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This loop also handles CPUs where &lt;code&gt;simd.lanes(u32)&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt;. In that case we skip all of the SIMD code and the scalar loop processes the entire input. The original implementation remains both the fallback and the tail.&lt;/p&gt;
&lt;p&gt;That&#39;s step 5. It&#39;s just the normal loop.&lt;/p&gt;
&lt;h2&gt;Recap: The Common Shape&lt;/h2&gt;
&lt;p&gt;Let&#39;s map the entire implementation back to the five steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;@splat(0xF)&lt;/code&gt; broadcasts the comparison value into every lane.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;while&lt;/code&gt; loop loads &lt;code&gt;lanes&lt;/code&gt; values at a time.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;values &amp;gt; threshold&lt;/code&gt; compares every lane in parallel.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@reduce&lt;/code&gt;, &lt;code&gt;@bitCast&lt;/code&gt;, and &lt;code&gt;@ctz&lt;/code&gt; find the first failed comparison.&lt;/li&gt;
&lt;li&gt;The original scalar loop handles the remainder and unsupported CPUs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The details in step 4 initially take some time to understand, but the overall shape is straightforward. And steps 1, 2, 3, and 5 tend to look nearly identical across completely different algorithms.&lt;/p&gt;
&lt;p&gt;Whenever you see a &lt;code&gt;for (byte in bytes)&lt;/code&gt;, this is the shape you&#39;ll map to.&lt;/p&gt;
&lt;h2&gt;Why Can&#39;t the Compiler Do This?&lt;/h2&gt;
&lt;p&gt;Sometimes it can! Compilers can &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://llvm.org/docs/Vectorizers.html&quot;&gt;auto-vectorize&lt;/a&gt; simple loops, particularly regular arithmetic loops without complex control flow. You should always compile the scalar version with optimizations and see what your compiler produces before manually writing SIMD.&lt;/p&gt;
&lt;p&gt;But compilers are severely limited in what they can auto-vectorize and are in general very poor at it. Auto-vectorization has been an active area of compiler research for decades, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://arxiv.org/abs/2406.04693&quot;&gt;recent research&lt;/a&gt; still begins from the observation that production compilers regularly miss vectorization opportunities. This isn&#39;t a problem I expect to disappear soon.&lt;/p&gt;
&lt;p&gt;More importantly, when this loop matters enough for me to care about a 5x speedup, I want the vectorization to be explicit and predictable. I don&#39;t want an unrelated code change or compiler update to quietly turn it back into a scalar loop.&lt;/p&gt;
&lt;h2&gt;Everyone Should Know SIMD&lt;/h2&gt;
&lt;p&gt;Every developer should be able to recognize the opportunity and, most importantly, should &lt;em&gt;not be scared of SIMD&lt;/em&gt;. If you see a hot loop scanning, comparing, counting, or transforming a large amount of contiguous data, you should be able to imagine processing it a vector-width chunk at a time.&lt;/p&gt;
&lt;p&gt;This post demonstrates that these common cases follow a very regular pattern that you quickly get used to. And with good language support, you don&#39;t need to know any assembly or CPU-specific quirks to get easy improvements.&lt;/p&gt;
&lt;p&gt;Everyone should know SIMD enough to do this.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fn-2&quot;&gt;5&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Very impressive projects like simdutf and simdjson use extremely complex SIMD tricks to achieve their goals. But this isn&#39;t what I&#39;d consider &amp;quot;everyday SIMD.&amp;quot; &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;C0 controls extend beyond &lt;code&gt;0xF&lt;/code&gt;. This is the cutoff Ghostty uses for this specific code path; ESC and other control-sequence handling happens elsewhere. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generic vectors remove the CPU-specific syntax, not CPU-specific code generation. Zig still lowers these operations to the instruction set enabled for the target. Ghostty falls back to scalar code when it can&#39;t choose a supported vector width. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The comparison itself is one vector operation. Loading the vector, reducing the result, and locating the failed lane require additional instructions. The important part is that we&#39;re doing multiple comparisons at once. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fnref-5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This post was based on a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://lobste.rs/s/gkcjic/simd_for_collision#c_pwdpep&quot;&gt;Lobsters comment I wrote&lt;/a&gt;. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/everyone-should-know-simd#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:d67c601274b85510834b2e1aa72fb4f1f945799b</id>
    <title type="text">Pledging Another $400,000 to the Zig Software Foundation</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-06-21-pledging-another-400-000-to-the-zig-software-foundation/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/zig-donation-2026"/>
    <published>2026-06-21T00:00:00+00:00</published>
    <updated>2026-06-21T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">My family is pledging another $400,000 1 to the Zig Software Foundation (ZSF). This brings our total pledged support for ZSF to $700,000, after our initial donation in 2024. Zig continues to earn my respect as a technical project and as a…</summary>
    <content type="html">&lt;p&gt;My family is pledging another $400,000 &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-donation-2026#user-content-fn-1&quot;&gt;1&lt;/a&gt; to the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/zsf/&quot;&gt;Zig Software Foundation&lt;/a&gt; (ZSF). This brings our total pledged support for ZSF to $700,000, after our &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-donation&quot;&gt;initial donation in 2024&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Zig continues to earn my respect as a technical project and as a community. The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/devlog/2026/&quot;&gt;2026 devlog&lt;/a&gt; shows steady progress on the hard problems of building an excellent language and compiler. I also deeply respect the project&#39;s approach to maintainership and community, reflected in initiatives like Loris Cro&#39;s &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://kristoff.it/blog/contributor-poker-and-ai/&quot;&gt;Contributor Poker and Zig&#39;s AI Ban&lt;/a&gt;. That philosophy continues to attract and develop some of the most &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/news/welcoming-new-team-members/&quot;&gt;talented people in open source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently, Zig&#39;s strict no-LLM contribution policy became a public topic of discussion again, especially in the context of Bun&#39;s Zig fork and Rust rewrite. I have no problem with what Bun did, I think Bun is a great project, and I&#39;m not interested in turning this into a Bun post. Instead, what stood out to me was how quickly people villainized one another. Too much of the conversation lacked empathy and respect for viewpoints different from our own.&lt;/p&gt;
&lt;p&gt;I use AI heavily. I&#39;ve written about &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey&quot;&gt;my AI adoption journey&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing&quot;&gt;shipping real features with AI assistance&lt;/a&gt;. I&#39;m also quite vocal about &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://x.com/mitchellh/status/2066657032938442833&quot;&gt;remaining rational about its capabilities&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/mitchellh/vouch&quot;&gt;frustrated with its negative impacts on open source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The point is that I have opinions. Those opinions don&#39;t fully align with ZSF&#39;s approach. And yet, I have nothing but respect for ZSF: the people, the policies, and the project. Part of what makes the internet and open source great is that projects can be weird and different. They can set unusual boundaries, build their own culture, and pursue quality in ways that won&#39;t make sense to everyone.&lt;/p&gt;
&lt;p&gt;Zig is exceptional software: ambitious, practical, independent, and unusually serious about quality. Ghostty exists in large part because Zig made it possible for me to build the kind of software I wanted to build. This is why I support Zig.&lt;/p&gt;
&lt;p&gt;I&#39;m proud to support Zig and the Zig Software Foundation again. Please consider &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/zsf/&quot;&gt;donating&lt;/a&gt; if you can.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;$200,000 per year split over two years, the same structure as our 2024 donation. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-donation-2026#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:00f70f79ef44e71672a830a7a15d1929a479d375</id>
    <title type="text">Ghostty Is Leaving GitHub</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-04-28-ghostty-is-leaving-github/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/ghostty-leaving-github"/>
    <published>2026-04-28T00:00:00+00:00</published>
    <updated>2026-04-28T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">Writing this makes me irrationally sad, but Ghostty will be leaving GitHub 1. I&#39;m GitHub user 1299, joined Feb 2008. Since then, I&#39;ve opened GitHub every single day. Every day, multiple times per day, for over 18 years. Over half my life.…</summary>
    <content type="html">&lt;p&gt;Writing this makes me irrationally sad, but Ghostty will be leaving GitHub &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-leaving-github#user-content-fn-3&quot;&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;m GitHub user 1299, joined Feb 2008.&lt;/p&gt;
&lt;p&gt;Since then, I&#39;ve opened GitHub every single day. Every day, multiple times per day, for over 18 years. Over half my life. A handful of exceptions in there (I&#39;d love to see the data), but I can&#39;t imagine more than a week per year.&lt;/p&gt;
&lt;p&gt;GitHub is the place that has made me the most happy. I always made time for it. When I went through tough breakups? I lost myself in open source... on GitHub. During college at 4 AM when everyone is passed out? Let me get one commit in. During my honeymoon while my wife is still asleep? Yeah, GitHub. It&#39;s where I&#39;ve historically been happiest and wanted to be.&lt;/p&gt;
&lt;p&gt;Even the annoying stuff! Some people doom scroll social media. I&#39;ve been doom scrolling GitHub issues since before that was a word. On vacations I&#39;d have bookmarks of different projects on GitHub I wanted to study. Not just source code, but OSS processes, how other maintainers react to difficult situations. Etc. Believe it or not, I like this.&lt;/p&gt;
&lt;p&gt;Some might call this sick, but my hobby and work and passion all align and for most of my life they got to also live in one place on the internet: GitHub.&lt;/p&gt;
&lt;p&gt;Did you know I started Vagrant (my first successful open source project) in large part because I hoped it would get me a job at GitHub? It&#39;s no secret, I&#39;ve said this repeatedly, and in my first public talk about Vagrant, when I was a mere 20 years old, I joked &amp;quot;maybe GitHub will hire me if it&#39;s good!&amp;quot;&lt;/p&gt;
&lt;p&gt;GitHub was my dream job. I didn&#39;t ever get to work there (not their fault). But it was the perfect place I wanted to be. The engineers were incredible, the product was incredible, and it was something I lived and breathed every day. I still do and consistently have... for these 18 years. Enough time for an entire human to become an adult, all on GitHub.&lt;/p&gt;
&lt;p&gt;Lately, I&#39;ve been very publicly critical of GitHub. I&#39;ve been mean about it. I&#39;ve been angry about it. I&#39;ve hurt people&#39;s feelings. I&#39;ve been lashing out. Because GitHub is failing me, every single day, and it is personal. It is irrationally personal. I love GitHub more than a person should love a thing, and I&#39;m mad at it. I&#39;m sorry about the hurt feelings to the people working on it.&lt;/p&gt;
&lt;p&gt;I&#39;ve felt this way for a long time, but for the past month I&#39;ve kept a journal where I put an &amp;quot;X&amp;quot; next to every date where a GitHub outage has negatively impacted my ability to work &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-leaving-github#user-content-fn-2&quot;&gt;2&lt;/a&gt;. Almost every day has an X. On the day I am writing this post, I&#39;ve been unable to do any PR review for ~2 hours because there is a GitHub Actions outage &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-leaving-github#user-content-fn-1&quot;&gt;3&lt;/a&gt;. This is no longer a place for serious work if it just blocks you out for hours per day, every day.&lt;/p&gt;
&lt;p&gt;It&#39;s not a fun place for me to be anymore. I want to be there but it doesn&#39;t want me to be there. I want to get work done and it doesn&#39;t want me to get work done. I want to ship software and it doesn&#39;t want me to ship software.&lt;/p&gt;
&lt;p&gt;I want it to be better, but I also want to code. And I can&#39;t code with GitHub anymore. I&#39;m sorry. After 18 years, I&#39;ve got to go. I&#39;d love to come back one day, but this will have to be predicated on real results and improvements, not words and promises.&lt;/p&gt;
&lt;p&gt;I&#39;ll share more details about where the Ghostty project will be moving to in the coming months. We have a plan but I&#39;m also very much still in discussions with multiple providers (both commercial and FOSS).&lt;/p&gt;
&lt;p&gt;It&#39;ll take us time to remove all of our dependencies on GitHub and we have a plan in place to do it as incrementally as possible. We plan on keeping a read-only mirror available on GitHub at the current URL.&lt;/p&gt;
&lt;p&gt;My personal projects and other work will remain on GitHub for now. Ghostty is where I, our maintainers, and our open source community are most impacted so that is the focus of this change. We&#39;ll see where it goes after that.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The timing of this is coincidental with the large outage on April 27, 2026. We&#39;ve been discussing and putting together a plan to leave GitHub for months, and this blog post was written over a week ago. We only made the final decision this week. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-leaving-github#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To the &amp;quot;Git is distributed!&amp;quot; crowd: the issue isn&#39;t Git, it&#39;s the infrastructure we rely on around it: issues, PRs, Actions, etc. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-leaving-github#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is not the large Elasticsearch outage they had on April 27, 2026. This blog post was written a week before that, so this was a different outage. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-leaving-github#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:9208cebd5b5530c449da3a8870b2355c9c7b5723</id>
    <title type="text">Simdutf Can Now Be Used Without libc++ or libc++abi</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-04-15-simdutf-can-now-be-used-without-libc-or-libc-abi/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/simdutf-no-libcxx"/>
    <published>2026-04-15T00:00:00+00:00</published>
    <updated>2026-04-15T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">As of this PR, simdutf can be used without libc++ or libc++abi 1. Simdutf was the final remaining libc++ dependency in libghostty-vt2. After updating Ghostty to use this new simdutf build, we were able to remove libc++ and libc++abi…</summary>
    <content type="html">&lt;p&gt;As of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/simdutf/simdutf/pull/959#top&quot;&gt;this PR&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/simdutf/simdutf&quot;&gt;simdutf&lt;/a&gt; can be used without libc++ or libc++abi &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fn-1&quot;&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Simdutf was the final remaining libc++ dependency in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://libghostty.tip.ghostty.org&quot;&gt;libghostty-vt&lt;/a&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fn-2&quot;&gt;2&lt;/a&gt;. After &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/12291&quot;&gt;updating Ghostty to use this new simdutf build&lt;/a&gt;, we were able to remove libc++ and libc++abi completely from our dependencies.&lt;/p&gt;
&lt;h2&gt;The Benefits of Not Depending on libc++&lt;/h2&gt;
&lt;p&gt;Not depending on libc++ makes a library more portable (embedded, WebAssembly, freestanding environments), simplifies cross-compilation (no target-specific C++ standard library needed), reduces binary size, and can simplify static linking.&lt;/p&gt;
&lt;p&gt;As a general purpose, low-level library, simdutf should be as portable and flexible as possible. If a downstream consumer can easily use libc++, great! But if they can&#39;t, they shouldn&#39;t be blocked from using simdutf since it fundamentally doesn&#39;t need it.&lt;/p&gt;
&lt;h2&gt;libc++ vs libc++abi&lt;/h2&gt;
&lt;p&gt;There are two parts to extricating a program from libc++.&lt;/p&gt;
&lt;p&gt;First, &lt;code&gt;libc++&lt;/code&gt; is the C++ standard library, which provides things like &lt;code&gt;std::vector&lt;/code&gt;, &lt;code&gt;std::string&lt;/code&gt;, etc. If you import &lt;code&gt;&amp;lt;vector&amp;gt;&lt;/code&gt; or even the libc C++ fallbacks like &lt;code&gt;&amp;lt;cstring&amp;gt;&lt;/code&gt;, you&#39;re depending on libc++.&lt;/p&gt;
&lt;p&gt;The sneakier part is &lt;code&gt;libc++abi&lt;/code&gt;, which provides the C++ ABI, including things like exception handling, virtual function tables, RTTI, etc. If you use any C++ features that require these, you&#39;re depending on libc++abi, even if you don&#39;t import any C++ standard library headers &lt;em&gt;at all&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For example, the minimal C++ program below depends on libc++abi because it uses function-local static variables, which require thread-safe initialization that is part of the C++ ABI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;CSS&quot;&gt;&lt;span class=&quot;syntax-source syntax-css&quot;&gt;&lt;span class=&quot;syntax-meta syntax-selector syntax-css&quot;&gt;struct Implementation &lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property-list syntax-css&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;version&lt;/span&gt;;
&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;}&lt;/span&gt;;

&lt;span class=&quot;syntax-meta syntax-selector syntax-css&quot;&gt;const Implementation&amp;amp; get_impl(&lt;/span&gt;) &lt;span class=&quot;syntax-meta syntax-property-list syntax-css&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;const&lt;/span&gt; I&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;mplementation&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;impl&lt;/span&gt;{1&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;}&lt;/span&gt;;
&lt;span class=&quot;syntax-meta syntax-selector syntax-css&quot;&gt;    return impl;
}

int &lt;span class=&quot;syntax-entity syntax-name syntax-tag syntax-css&quot;&gt;main&lt;/span&gt;(&lt;/span&gt;) &lt;span class=&quot;syntax-meta syntax-property-list syntax-css&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;get&lt;/span&gt;_&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;impl&lt;/span&gt;().&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;version&lt;/span&gt;;
&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Extricating simdutf from libc++&lt;/h2&gt;
&lt;p&gt;Let&#39;s talk about &lt;code&gt;libc++&lt;/code&gt; first (not the ABI).&lt;/p&gt;
&lt;p&gt;simdutf is a C++ library that makes heavy use of the latest and greatest C++ features and though I don&#39;t know him personally, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://lemire.me/en/&quot;&gt;Daniel Lemire&lt;/a&gt; seems to really love utilizing C++ features to their fullest extent. So, simdutf is very much a C++ project.&lt;/p&gt;
&lt;p&gt;For there to be any chance that my changes would be accepted, I had to make sure that the project could continue to use C++ features without it being a headache.&lt;/p&gt;
&lt;h3&gt;STL Usage&lt;/h3&gt;
&lt;p&gt;The approach I decided to take was to introduce a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/mitchellh/simdutf/blob/nolibcxx/include/simdutf/internal/stl_compat.h&quot;&gt;&lt;code&gt;stl_compat.h&lt;/code&gt; header&lt;/a&gt; that centralizes all of the C++ standard library types. In normal libc++ mode, everything in &lt;code&gt;stl_compat.h&lt;/code&gt; is a simple include or minimal alias to the corresponding C++ standard library type with no runtime overhead.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;NO_LIBCXX&lt;/code&gt; mode, &lt;code&gt;stl_compat.h&lt;/code&gt; provides its own implementations of the C++ types that simdutf uses, but only just enough for them to be compatible with what simdutf needs. For example, &lt;code&gt;stl_compat.h&lt;/code&gt; provides its own implementation of &lt;code&gt;std::pair&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As a result, the changes required end up mostly looking like this throughout the diff:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;-std::pair&amp;lt;const char *, char32_t *&amp;gt;
+internal::pair&amp;lt;const char *, char32_t *&amp;gt;
arm_convert_latin1_to_utf32(const char *buf, size_t len,
                            char32_t *utf32_output) {
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ABI Compatibility&lt;/h3&gt;
&lt;p&gt;My goal was to retain as much ABI compatibility as possible. Some of the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/Application_binary_interface&quot;&gt;public ABI&lt;/a&gt; expose C++ types such as &lt;code&gt;std::string&lt;/code&gt;, so in those scenarios, ABI had to be broken. Otherwise, it is fully retained.&lt;/p&gt;
&lt;p&gt;Given &lt;code&gt;SIMDUTF_NO_LIBCXX&lt;/code&gt; is a new feature that applies changes to the compilation unit, I felt that it was acceptable to break ABI &lt;em&gt;only when this flag is present&lt;/em&gt;. In the existing case where the &lt;code&gt;NO_LIBCXX&lt;/code&gt; flag is not present, the ABI is fully retained and simdutf can be updated without breaking ABI for existing users.&lt;/p&gt;
&lt;p&gt;I was pleasantly surprised to find that the ABI breakage was very minimal, and only applied to a handful of diagnostic functions (e.g. to get the name of the active implementation) and helpers to work with other C++ types (e.g. text encoding &lt;code&gt;std::string&lt;/code&gt;). Since by definition someone using &lt;code&gt;SIMDUTF_NO_LIBCXX&lt;/code&gt; is not interested in libc++, these ABI breakages felt like a feature rather than a bug.&lt;/p&gt;
&lt;h2&gt;Extricating simdutf from libc++abi&lt;/h2&gt;
&lt;p&gt;This task was significantly more complex.&lt;/p&gt;
&lt;p&gt;The main problem was that &lt;code&gt;libc++abi&lt;/code&gt; dependencies don&#39;t usually show up as obvious source-level includes. They show up because the compiler quietly emits calls into the C++ ABI runtime for ordinary-looking language features. To detect these, I had to write a script that decompiles the object files and looks for symbols such as &lt;code&gt;__cxa_guard_acquire&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The biggest offender in simdutf was the runtime dispatch layer. The original code relied heavily on function-local static variables. In C++, those locals are guarded by thread-safe initialization helpers such as &lt;code&gt;__cxa_guard_acquire&lt;/code&gt; and &lt;code&gt;__cxa_guard_release&lt;/code&gt;, which are provided by the C++ ABI runtime. So even though the code didn&#39;t mention libc++abi anywhere, the compiled object still depended on it. The fix for this is to use translation-unit static variables instead of function-local statics in &lt;code&gt;NO_LIBCXX&lt;/code&gt; mode.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;#if SIMDUTF_IMPLEMENTATION_ICELAKE
  #ifdef SIMDUTF_NO_LIBCXX
static const icelake::implementation icelake_singleton{};
  #endif
static const icelake::implementation *get_icelake_singleton() {
  #ifdef SIMDUTF_NO_LIBCXX
  return &amp;amp;icelake_singleton;
  #else
  static const icelake::implementation icelake_singleton{};
  return &amp;amp;icelake_singleton;
  #endif
}
#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, simdutf models each backend as a subclass of an abstract &lt;code&gt;implementation&lt;/code&gt; interface. That design can stay, but abstract-class vtables still reference &lt;code&gt;__cxa_pure_virtual&lt;/code&gt; for impossible-to-call pure virtual entries. In &lt;code&gt;SIMDUTF_NO_LIBCXX&lt;/code&gt; mode I chose to provide a tiny local shim and ensure the runtime never actually reaches it. I marked this as weak so that the symbol can be overridden by the C++ ABI if it is present.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;#ifdef SIMDUTF_NO_LIBCXX
// The abstract implementation vtable still carries pure-virtual slots even
// though correct dispatch never reaches them in this build mode. Provide the
// narrowest possible ABI shim so stricter no-libcxx objects do not require
// libc++abi just for this unreachable hook. Keep it weak so a toolchain&amp;#39;s real
// libc++abi definition wins if one is linked in anyway.
extern &amp;quot;C&amp;quot; SIMDUTF_WEAK [[noreturn]] void __cxa_pure_virtual() noexcept {
  __builtin_trap();
}
#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, I wrote a script to audit the build with &lt;code&gt;-fno-exceptions&lt;/code&gt; and &lt;code&gt;-fno-rtti&lt;/code&gt; and check that no symbols such as &lt;code&gt;__cxa_guard_*&lt;/code&gt;, &lt;code&gt;__gxx_personality&lt;/code&gt;, &lt;code&gt;__cxa_throw&lt;/code&gt;, &lt;code&gt;typeinfo&lt;/code&gt;, or &lt;code&gt;__dynamic_cast&lt;/code&gt; ever show up. This was added to the simdutf CI to ensure that we never accidentally reintroduce libc++abi dependencies in the future for &lt;code&gt;NO_LIBCXX&lt;/code&gt; builds.&lt;/p&gt;
&lt;h2&gt;Validation&lt;/h2&gt;
&lt;h3&gt;Internal&lt;/h3&gt;
&lt;p&gt;simdutf is a correctness and performance critical library, so I had to be sure my changes didn&#39;t impact either of those. I modified the pre-existing test and benchmark suites to run in both &lt;code&gt;NO_LIBCXX&lt;/code&gt; and normal modes and ensured that all tests passed and benchmarks were unaffected.&lt;/p&gt;
&lt;p&gt;The important part about this is that I committed the changes necessary to ensure that the &lt;code&gt;NO_LIBCXX&lt;/code&gt; mode is compatible with the existing test and benchmark suites. This means that future changes to simdutf can continue to validate both.&lt;/p&gt;
&lt;h3&gt;External: Ghostty&lt;/h3&gt;
&lt;p&gt;Next, I updated Ghostty to use the new simdutf from my fork, updated our builds to use &lt;code&gt;SIMDUTF_NO_LIBCXX&lt;/code&gt;, and added our own suite of tests to verify our artifacts have no dependencies on libc++ or libc++abi.&lt;/p&gt;
&lt;p&gt;Ghostty has a robust set of tests that verify our UTF-8 decoding behavior (especially invalid inputs). And Ghostty has a built-in benchmark suite that tests our UTF-8 throughput in a variety of scenarios. I ran all the Ghostty tests and benchmarks and verified that they all passed and that our UTF-8 performance was unaffected, as expected.&lt;/p&gt;
&lt;h2&gt;Pull Request&lt;/h2&gt;
&lt;p&gt;Getting something working and getting something merged are two different things.&lt;/p&gt;
&lt;p&gt;I know too well as a maintainer myself the disparity between &amp;quot;this works&amp;quot; and &amp;quot;this can be merged.&amp;quot; I know the challenges of verifying someone&#39;s work and being confident in maintaining it going forward. I know the challenge of opening a large PR and not having clarity of why it is being opened. And I know the burden of recent AI slop.&lt;/p&gt;
&lt;p&gt;So I put in the effort I would want from an all-star contributor and tried to be that person for the simdutf maintainers.&lt;/p&gt;
&lt;p&gt;First, I reviewed the entire diff (yes, all ~3,000 lines of it). Then I reviewed it again. I re-read the entire diff by hand three or four times. I made multiple changes based on things I probably would&#39;ve commented on myself, even if functionally it was fine.&lt;/p&gt;
&lt;p&gt;Next, I hand-wrote a detailed PR description that explains the motivation, the approach, the limitations, and the validation. I wanted to make sure that the maintainers were aware of the details but also how much thought I put into the details.&lt;/p&gt;
&lt;p&gt;Finally, I disclosed that I did use AI to assist me in writing the code. But I made it clear that I manually reviewed everything, I didn&#39;t use AI in writing any of the PR description or comments, and that I was capable and comfortable as a human to defend and modify any proposed changes.&lt;/p&gt;
&lt;p&gt;Ironically, the full diff took me about 2 hours to put together, but the extra validation work and PR preparation took me about 3 hours. I spent more time on the human boundary than the code itself, as we should out of respect for the effort maintainers put into their projects.&lt;/p&gt;
&lt;h2&gt;Final Status&lt;/h2&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/simdutf/simdutf/pull/959&quot;&gt;simdutf PR&lt;/a&gt; is still under review. Initial feedback is positive, and I&#39;m open to any and all changes requested of me. There&#39;s a possibility that the maintainers will not want to merge it, and that&#39;s okay, too.&lt;/p&gt;
&lt;p&gt;If you want to use simdutf without libc++ or libc++abi, you can &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/mitchellh/simdutf/tree/nolibcxx&quot;&gt;use my fork&lt;/a&gt; in the meantime. The same instructions for producing an amalgamated single-file plus header build apply. When building the C++ and including the header, just make sure to define &lt;code&gt;SIMDUTF_NO_LIBCXX&lt;/code&gt; to get the no-libc++ version of the library.&lt;/p&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/12291&quot;&gt;Ghostty PR&lt;/a&gt; is now merged. As such, &lt;code&gt;libghostty-vt&lt;/code&gt; no longer depends on libc++ or libc++abi for SIMD builds.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;libc++ is the C++ standard library (e.g. &lt;code&gt;std::vector&lt;/code&gt;, &lt;code&gt;std::string&lt;/code&gt;, etc.) and libc++abi is the C++ ABI library (e.g. exception handling, RTTI, etc.). &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Note that with SIMD disabled, libghostty-vt has always had zero dependencies, not even libc. It is a fully freestanding library. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:788d807e5ce658d7b1e52dd9a57c25122f1d58d2</id>
    <title type="text">The Building Block Economy</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-04-07-the-building-block-economy/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/building-block-economy"/>
    <published>2026-04-07T00:00:00+00:00</published>
    <updated>2026-04-07T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">The most effective way to build software and get massive adoption is no longer high quality mainline apps but via building blocks that enable and encourage others to build quantity over quality.1 Ghostty in 18 months: one million daily…</summary>
    <content type="html">&lt;p&gt;The most effective way to build software and get massive adoption is no longer high quality mainline apps but via building blocks that enable and encourage others to build quantity over quality.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy#user-content-fn-4&quot;&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ghostty in &lt;strong&gt;18 months&lt;/strong&gt;: one million daily macOS update checks.&lt;/li&gt;
&lt;li&gt;libghostty in &lt;strong&gt;2 months&lt;/strong&gt;: multiple millions of daily users.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy#user-content-fn-1&quot;&gt;2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similar growth trajectories can be seen in other &amp;quot;building block&amp;quot; technologies: Pi Mono, Next.js, Tailwind, etc.&lt;/p&gt;
&lt;p&gt;Experiencing this firsthand as well as witnessing it in other ecosystems has fundamentally shifted how I view the practice of product and software development today, regardless of commercial vs non-commercial goals.&lt;/p&gt;
&lt;p&gt;This article was written by hand, without the assistance of AI. I love and use AI abundantly, but I draw the line personally at content like this. I want my personal blog to reflect my genuine thoughts and feelings.&lt;/p&gt;
&lt;h2&gt;Imports Are Up&lt;/h2&gt;
&lt;p&gt;I use the term &amp;quot;building block&amp;quot; to describe how they&#39;re being used because they&#39;re being assembled today in a very different way than former decades. I don&#39;t use the term &amp;quot;library&amp;quot; or &amp;quot;framework&amp;quot; because it extends even up to &amp;quot;applications&amp;quot; (e.g. the Ghostty GUI app has more forks than ever before with customized patches on top, which is awesome).&lt;/p&gt;
&lt;p&gt;The factory of today is agentic &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy#user-content-fn-3&quot;&gt;3&lt;/a&gt;. I say that as objective truth, regardless of what your feelings about it are. You can argue that 99% of the stuff coming out of these factories is total crap, but you can&#39;t argue the sheer quantity of stuff coming out. The numbers are everywhere spanning tech stacks and industries and they&#39;re undeniable.&lt;/p&gt;
&lt;p&gt;AI is okay at building everything from scratch, but it is &lt;em&gt;really good&lt;/em&gt; at gluing together high quality, well documented, and proven components. And, AI prefers to do this when it can unless explicitly prompted otherwise. This is the &amp;quot;building block&amp;quot; nature of software today: we&#39;re more than ever before grabbing off the shelf components and gluing them together.&lt;/p&gt;
&lt;p&gt;Humans, of course, have always done this as well. For my entire career, human software developers have preferred to build on top of proven primitives. But the natural barrier to entry of understanding the component pieces well enough to even slap them together was high enough to limit the ecosystem. This barrier is now gone.&lt;/p&gt;
&lt;h2&gt;Exports Are Up&lt;/h2&gt;
&lt;p&gt;Coming out of these factories is of course software. So much software.&lt;/p&gt;
&lt;p&gt;There are negatives to this. I think the negatives are obvious enough that I&#39;m not going to dedicate much time to them, but I want to recognize they exist: security vulnerabilities, instability, a general lack of understanding about how load-bearing systems might work.&lt;/p&gt;
&lt;p&gt;But there are a huge amount of positives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The quality bar is lower.&lt;/strong&gt; A mainline application used by a large cross-section of users has to weigh every feature against every other feature: how do they interact? does it make sense for the long term vision? can I maintain this for millions of users? A factory artifact targeting one to hundreds of users doesn&#39;t need to care about this. You can ship faster and looser, as a result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The awareness is greater.&lt;/strong&gt; A mainline application can&#39;t do everything. It usually optimizes for the use cases that the most users need and use. A factory artifact can optimize for a tiny cross-section of users, and these users gain awareness of the building block as a result. I&#39;m seeing this hugely in Ghostty, as very niche communities are getting terminals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The maintenance burden is lower.&lt;/strong&gt; It is easier than ever to say &amp;quot;no thank you&amp;quot; to feature requests, because you&#39;re offering a key part of the means to production. My challenges with slop requests are very public, to the point I made a &amp;quot;no machine&amp;quot;, but I&#39;m also feeling less and less bad every day about saying &amp;quot;no.&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;R&amp;amp;D is outsourced.&lt;/strong&gt; It is so much easier now as a maintainer to look at what others are doing, see working proof of concepts, and decide what you want to bring back to mainline. There&#39;s way less talk and way more walk. And while others walk you can cherry pick the best ideas (this is fair: you&#39;re giving away a building block and they&#39;re giving away their ideas).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Impact&lt;/h2&gt;
&lt;p&gt;This is changing how I view software and product development.&lt;/p&gt;
&lt;p&gt;I&#39;m much more purposeful about creating building blocks and encouraging applications or forks on top of that. I think this is resulting in a happier community, a larger community, and ultimately better mainline software.&lt;/p&gt;
&lt;p&gt;High-quality applications aren&#39;t disappearing. And high-quality applications produced by the developers of the building block aren&#39;t disappearing. For most software categories, I think there will always be a majority group that doesn&#39;t want personalized slop software and wants a polished, well maintained, and well supported application.&lt;/p&gt;
&lt;p&gt;Instead, I think the mainline application is becoming more stable and more purposeful in its feature set thanks to the building block economy. The stability comes from a much larger and diverse user group. The feature set comes from the massive ecosystem of outsourced R&amp;amp;D. The mainline application is still measured, high quality, and well maintained, but it&#39;s for a specific group.&lt;/p&gt;
&lt;h2&gt;The Elephant in the Room: Commercialization&lt;/h2&gt;
&lt;p&gt;The obvious question that follows is what this can mean for commercialization. Closed source, commercial software appears to be at a massive disadvantage. And it is.&lt;/p&gt;
&lt;p&gt;Agents will more readily pick open and free software over closed and commercial. At the time of writing this article, this is an objective truth. Independent research labs running experiments on popular models have found repeatedly that under diverse circumstances, models pick open and free alternatives over commercial. So far.&lt;/p&gt;
&lt;p&gt;But, I don&#39;t have a concrete answer here, because unlike product and software development, I&#39;m not directly building a commercializable product right now. I have thoughts, and as with all hard things, I think the answer is nuanced. But, I don&#39;t want to give the illusion of talking authoritatively about this so I&#39;m going to avoid it. When I walk the walk and learn more, I&#39;ll share more.&lt;/p&gt;
&lt;p&gt;I&#39;m once again simply acknowledging that this challenge obviously exists.&lt;/p&gt;
&lt;h2&gt;The Shift Has Happened&lt;/h2&gt;
&lt;p&gt;We have to accept that building blocks and software factories rule everything around us and accept and internalize the consequences of that.&lt;/p&gt;
&lt;p&gt;We can choose to run the other direction and create &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://drewdevault.com/2026/03/25/2026-03-25-Forking-vim.html&quot;&gt;enclaves where we fight against it&lt;/a&gt;. Or we can choose to &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://steve-yegge.medium.com/welcome-to-the-wasteland-a-thousand-gas-towns-a5eb9bc8dc1f&quot;&gt;submit ourselves completely to the chaos&lt;/a&gt;. People who know me know I&#39;m far less extreme in my actions and carry different opinions depending on context.&lt;/p&gt;
&lt;p&gt;The point is the shift has already happened. We&#39;re living in it.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The building blocks themselves must usually be high quality, robust, and well documented, though. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Getting exact numbers is obviously hard since Ghostty has no real tracking. We can see aggregate update file checks for macOS. We have no visibility at all into Linux. libghostty has no tracking but tools integrating libghostty might and have shared aggregates with us. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I use the term &amp;quot;agent&amp;quot; here to simply mean a large language model with access to tools in a loop. People sometimes think I&#39;m trying to be hype or cute by using marketing terms, but I am using this as a specific, well accepted definition. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/building-block-economy#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:bc6ab25f72e120c657ba181b28469f43671e4020</id>
    <title type="text">My AI Adoption Journey</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-02-05-my-ai-adoption-journey/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/my-ai-adoption-journey"/>
    <published>2026-02-05T00:00:00+00:00</published>
    <updated>2026-02-05T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">Table of Contents Step 1: Drop the Chatbot Step 2: Reproduce Your Own Work Step 3: End-of-Day Agents Step 4: Outsource the Slam Dunks Step 5: Engineer the Harness Step 6: Always Have an Agent Running Today My experience adopting any…</summary>
    <content type="html">&lt;p&gt;Table of Contents&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#step-1-drop-the-chatbot&quot;&gt;Step 1: Drop the Chatbot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#step-2-reproduce-your-own-work&quot;&gt;Step 2: Reproduce Your Own Work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#step-3-end-of-day-agents&quot;&gt;Step 3: End-of-Day Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#step-4-outsource-the-slam-dunks&quot;&gt;Step 4: Outsource the Slam Dunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#step-5-engineer-the-harness&quot;&gt;Step 5: Engineer the Harness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#step-6-always-have-an-agent-running&quot;&gt;Step 6: Always Have an Agent Running&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#today&quot;&gt;Today&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My experience adopting any meaningful tool is that I&#39;ve necessarily gone through three phases: (1) a period of inefficiency (2) a period of adequacy, then finally (3) a period of workflow and life-altering discovery.&lt;/p&gt;
&lt;p&gt;In most cases, I have to force myself through phase 1 and 2 because I usually have a workflow I&#39;m already happy and comfortable with. Adopting a tool feels like work, and I &lt;em&gt;do not&lt;/em&gt; want to put in the effort, but I usually do in an effort to be a well-rounded person of my craft.&lt;/p&gt;
&lt;p&gt;This is my journey of how I found value in AI tooling and what I&#39;m trying next with it. In an ocean of overly dramatic, hyped takes, I hope this represents a more nuanced, measured approach to my views on AI and how they&#39;ve changed over time.&lt;/p&gt;
&lt;p&gt;This blog post was fully written by hand, in my own words. I hate that I have to say that but especially given the subject matter, I want to be explicit about it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Step 1: Drop the Chatbot&lt;/h2&gt;
&lt;p&gt;Immediately cease trying to perform meaningful work via a chatbot (e.g. ChatGPT, Gemini on the web, etc.). Chatbots have real value and are a daily part of my AI workflow, but their utility in coding is highly limited because you&#39;re mostly hoping they come up with the right results based on their prior training, and correcting them involves a human (you) to tell them they&#39;re wrong repeatedly. It is inefficient.&lt;/p&gt;
&lt;p&gt;I think everyone&#39;s first experience with AI is a chat interface. And I think everyone&#39;s first experience trying to code with AI has been asking a chat interface to write code.&lt;/p&gt;
&lt;p&gt;While I was still a heavy AI skeptic, my first &amp;quot;oh wow&amp;quot; moment was pasting a screenshot of Zed&#39;s command palette into Gemini, asking it to reproduce it with SwiftUI, and being truly flabbergasted that it did it &lt;em&gt;very well&lt;/em&gt;. The command palette that ships for macOS in Ghostty today is only very lightly modified from what Gemini produced for me in seconds.&lt;/p&gt;
&lt;p&gt;But when I tried to reproduce that behavior for other tasks, I was left disappointed. In the context of brownfield projects, I found the chat interface produced poor results very often, and I found myself very frustrated copying and pasting code and command output to and from the interface. It was very obviously far less efficient than me doing the work myself.&lt;/p&gt;
&lt;p&gt;To find value, you &lt;em&gt;must&lt;/em&gt; use an &lt;strong&gt;agent&lt;/strong&gt;. An agent is the industry-adopted term for an LLM that can chat and invoke external behavior in a loop &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fn-1&quot;&gt;1&lt;/a&gt; At a bare minimum, the agent must have the ability to: read files, execute programs, and make HTTP requests.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Step 2: Reproduce Your Own Work&lt;/h2&gt;
&lt;p&gt;The next phase on my journey I tried &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/anthropics/claude-code&quot;&gt;Claude Code&lt;/a&gt;. I&#39;ll cut to the chase: I initially wasn&#39;t impressed. I just wasn&#39;t getting good results out of my sessions. I felt I had to touch up everything it produced and this process was taking more time than if I had just done it myself. I read blog posts, watched videos, but just wasn&#39;t that impressed.&lt;/p&gt;
&lt;p&gt;Instead of giving up, I &lt;strong&gt;forced myself to reproduce all my manual commits with agentic ones.&lt;/strong&gt; I literally did the work twice. I&#39;d do the work manually, and then I&#39;d fight an agent to produce identical results in terms of quality and function (without it being able to see my manual solution, of course).&lt;/p&gt;
&lt;p&gt;This was &lt;em&gt;excruciating&lt;/em&gt;, because it got in the way of simply getting things done. But I&#39;ve been around the block with non-AI tools enough to know that friction is natural, and I can&#39;t come to a firm, defensible conclusion without exhausting my efforts.&lt;/p&gt;
&lt;p&gt;But, expertise formed. I quickly discovered for myself from first principles what others were already saying, but discovering it myself resulted in a stronger fundamental understanding.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Break down sessions into separate clear, actionable tasks. Don&#39;t try to &amp;quot;draw the owl&amp;quot; in one mega session.&lt;/li&gt;
&lt;li&gt;For vague requests, split the work into separate planning vs. execution sessions.&lt;/li&gt;
&lt;li&gt;If you give an agent a way to verify its work, it more often than not fixes its own mistakes and prevents regressions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;More generally, I also found the edges of what agents -- at the time -- were good at, what they weren&#39;t good at, and for the tasks they were good at how to achieve the results I wanted.&lt;/p&gt;
&lt;p&gt;All of this led to significant efficiency gains, to the point where I was starting to naturally use agents in a way that I felt was no slower than doing it myself (but I still didn&#39;t feel it was any faster, since I was mostly babysitting an agent).&lt;/p&gt;
&lt;p&gt;The negative space here is worth reiterating: part of the efficiency gains here were understanding when &lt;em&gt;not&lt;/em&gt; to reach for an agent. Using an agent for something it&#39;ll likely fail at is obviously a big waste of time and having the knowledge to avoid that completely leads to time savings &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fn-3&quot;&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At this stage, I was finding adequate value with agents that I was happy to use them in my workflow, but still didn&#39;t feel like I was seeing any net efficiency gains. I didn&#39;t care though, I was content at this point with AI as a tool.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Step 3: End-of-Day Agents&lt;/h2&gt;
&lt;p&gt;To try to find some efficiency, I next started up a new pattern: &lt;strong&gt;block out the last 30 minutes of every day to kick off one or more agents.&lt;/strong&gt; My hypothesis was that &lt;em&gt;perhaps&lt;/em&gt; I could gain some efficiency if the agent can make some &lt;em&gt;positive progress&lt;/em&gt; in the times I can&#39;t work anyways. Basically: instead of trying to do more in the time I have, try to do more in the time I don&#39;t have.&lt;/p&gt;
&lt;p&gt;Similar to the previous task, I at first found this both unsuccessful and annoying. But, I once again quickly found different categories of work that were really helpful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deep research sessions&lt;/strong&gt; where I&#39;d ask agents to survey some field, such as finding all libraries in a specific language with a specific license type and producing multi-page summaries for each on their pros, cons, development activity, social sentiment, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parallel agents attempting different vague ideas I had but didn&#39;t have time to get started on.&lt;/strong&gt; I didn&#39;t expect them to produce something I&#39;d ever ship here, but perhaps could illuminate some unknown unknowns when I got to the task the next day.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Issue and PR triage/review.&lt;/strong&gt; Agents are good at using &lt;code&gt;gh&lt;/code&gt; (GitHub CLI), so I manually scripted a quick way to spin up a bunch in parallel to triage issues. I would NOT allow agents to respond, I just wanted reports the next day to try to guide me towards high value or low effort tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To be clear, I did not go as far as others went to have agents running in loops all night. In most cases, agents completed their tasks in less than half an hour. But, the latter part of the working day, I&#39;m usually tired and coming out of flow and find myself too personally inefficient, so shifting my effort to spinning up these agents I found gave me a &amp;quot;warm start&amp;quot; the next morning that got me working more quickly than I would&#39;ve otherwise.&lt;/p&gt;
&lt;p&gt;I was happy, and I was starting to feel like I was doing more than I was doing prior to AI, if only slightly.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Step 4: Outsource the Slam Dunks&lt;/h2&gt;
&lt;p&gt;By this point, I was getting very confident about what tasks my AI was and wasn&#39;t great at. I had really high confidence with certain tasks that the AI would achieve a mostly-correct solution. So the next step on my journey was: &lt;strong&gt;let agents do all of that work while I worked on other tasks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;More specifically, I would start each day by taking the results of my prior night&#39;s triage agents, filter them manually to find the issues that an agent will almost certainly solve well, and then keep them going in the background (one at a time, not in parallel).&lt;/p&gt;
&lt;p&gt;Meanwhile, &lt;strong&gt;I&#39;d work on something else.&lt;/strong&gt; I wasn&#39;t going to social media (any more than usual without AI), I wasn&#39;t watching videos, etc. I was in my own, normal, pre-AI deep thinking mode working on something I wanted to work on or had to work on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Very important at this stage: turn off agent desktop notifications.&lt;/strong&gt; Context switching is very expensive. In order to remain efficient, I found that it was my job as a human to be in control of when I interrupt the agent, not the other way around. Don&#39;t let the agent notify you. During natural breaks in your work, tab over and check on it, then carry on.&lt;/p&gt;
&lt;p&gt;Importantly, I think the &amp;quot;work on something else&amp;quot; helps counteract the highly publicized &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.anthropic.com/research/AI-assistance-coding-skills&quot;&gt;Anthropic skill formation paper&lt;/a&gt;. Well, you&#39;re trading off: not forming skills for the tasks you&#39;re delegating to the agent while continuing to form skills naturally in the tasks you continue to work on manually.&lt;/p&gt;
&lt;p&gt;At this point I was firmly in the &amp;quot;no way I can go back&amp;quot; territory. I felt more efficient, but even if I wasn&#39;t, the thing I liked the most was that I could now focus my coding and thinking on tasks I really loved while still adequately completing the tasks I didn&#39;t.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Step 5: Engineer the Harness&lt;/h2&gt;
&lt;p&gt;At risk of stating the obvious: agents are much more efficient when they produce the right result the first time, or at worst produce a result that requires minimal touch-ups. The most sure-fire way to achieve this is to give the agent fast, high quality tools to automatically tell it when it is wrong.&lt;/p&gt;
&lt;p&gt;I don&#39;t know if there is a broad industry-accepted term for this yet, but I&#39;ve grown to calling this &amp;quot;harness engineering.&amp;quot; It is the idea that anytime you find an agent makes a mistake, you take the time to engineer a solution such that the agent never makes that mistake again. I don&#39;t need to invent any new terms here; if another one exists, I&#39;ll jump on the bandwagon.&lt;/p&gt;
&lt;p&gt;This comes in two forms:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Better implicit prompting (AGENTS.md).&lt;/strong&gt; For simple things, like the agent repeatedly running the wrong commands or finding the wrong APIs, update the &lt;code&gt;AGENTS.md&lt;/code&gt; (or equivalent). Here is &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/ca07f8c3f775fe437d46722db80a755c2b6e6399/src/inspector/AGENTS.md&quot;&gt;an example from Ghostty&lt;/a&gt;. Each line in that file is based on a bad agent behavior, and it almost completely resolved them all.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Actual, programmed tools.&lt;/strong&gt; For example, scripts to take screenshots, run filtered tests, etc etc. This is usually paired with an AGENTS.md change to let it know about this existing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;This is where I&#39;m at today.&lt;/strong&gt; I&#39;m making an earnest effort whenever I see an agent do a Bad Thing to prevent it from ever doing that bad thing again. Or, conversely, I&#39;m making an earnest effort for agents to be able to verify they&#39;re doing a Good Thing.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Step 6: Always Have an Agent Running&lt;/h2&gt;
&lt;p&gt;Simultaneous to step 5, I&#39;m also operating under the goal of &lt;strong&gt;having an agent running at all times.&lt;/strong&gt; If an agent isn&#39;t running, I ask myself &amp;quot;is there something an agent could be doing for me right now?&amp;quot;&lt;/p&gt;
&lt;p&gt;I particularly like to combine this with slower, more thoughtful models like Amp&#39;s &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/news/deep-mode&quot;&gt;deep mode&lt;/a&gt; (which is basically just GPT-5.2-Codex) which can take upwards of 30+ minutes to make small changes. The flip side of that is that it does tend to produce very good results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I&#39;m not [yet?] running multiple agents, and currently don&#39;t really want to.&lt;/strong&gt; I find having the one agent running is a good balance for me right now between being able to do deep, manual work I find enjoyable, and babysitting my kind of stupid and yet mysteriously productive robot friend.&lt;/p&gt;
&lt;p&gt;The &amp;quot;have an agent running at all times&amp;quot; goal is still just a goal. I&#39;d say right now I&#39;m maybe effective at having a background agent running 10 to 20% of a normal working day. But, I&#39;m actively working to improve that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don&#39;t want to run agents for the sake of running agents.&lt;/strong&gt; I only want to run them when there is a task I think would be truly helpful to me. Part of the challenge of this goal is improving my own workflows and tools so that I can have a constant stream of high quality work to do that I can delegate. Which, even without AI, is important!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Today&lt;/h2&gt;
&lt;p&gt;And that&#39;s where I&#39;m at today.&lt;/p&gt;
&lt;p&gt;Through this journey, I&#39;ve personally reached a point where I&#39;m having success with modern AI tooling and I believe I&#39;m approaching it with the proper measured view that is grounded in reality. I really don&#39;t care one way or the other if AI is here to stay &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fn-4&quot;&gt;3&lt;/a&gt;, I&#39;m a software craftsman that just wants to build stuff for the love of the game.&lt;/p&gt;
&lt;p&gt;The whole landscape is moving so rapidly that I&#39;m sure I&#39;ll look back at this post very quickly and laugh at my naivete. But, as they say, if you can&#39;t be embarassed about your past self, you&#39;re probably not growing. I just hope I&#39;ll grow in the right direction!&lt;/p&gt;
&lt;p&gt;I have no skin in the game here &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fn-5&quot;&gt;4&lt;/a&gt;, and there are of course other reasons behind utility to avoid using AI. I fully respect anyone&#39;s individual decisions regarding it. I&#39;m not here to convince you! For those interested, I just wanted to share my personal approach to navigating these new tools and give a glimpse about how I approach new tools &lt;em&gt;in general&lt;/em&gt;, regardless of AI.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Modern coding models like Opus and Codex are specifically trained to bias towards using tools compared to conversational models. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Due to the rapid pace of innovation in models, I have to constantly revisit my priors on this one. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The skill formation issues particularly in juniors without a strong grasp of fundamentals deeply worries me, however. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I don&#39;t work for, invest in, or advise any AI companies. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey#user-content-fnref-5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:53e1a1696428b801440f76d266e7fda3f9dc720b</id>
    <title type="text">Don&#39;t Trip[wire] Yourself: Testing Error Recovery in Zig</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-01-21-don-t-trip-wire-yourself-testing-error-recovery-in-zig/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/tripwire"/>
    <published>2026-01-21T00:00:00+00:00</published>
    <updated>2026-01-21T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">I&#39;ve written a library called Tripwire1 for injecting failures into Zig programs for the express purpose of testing error handling paths. Outside of unit tests, it is completely optimized away and has zero runtime cost (space or time). Zig…</summary>
    <content type="html">&lt;p&gt;I&#39;ve written a library called &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/src/tripwire.zig&quot;&gt;Tripwire&lt;/a&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/tripwire#user-content-fn-1&quot;&gt;1&lt;/a&gt; for injecting failures into Zig programs for the express purpose of testing error handling paths. Outside of unit tests, it is &lt;strong&gt;completely optimized away&lt;/strong&gt; and has zero runtime cost (space or time).&lt;/p&gt;
&lt;p&gt;Zig has a language feature &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#errdefer&quot;&gt;&lt;code&gt;errdefer&lt;/code&gt;&lt;/a&gt; for running code on block exit only when an error is being returned. The idea is simple: if an error occurs, you need to &amp;quot;undo&amp;quot; your partial effects so that when the function returns an error, the world state can be in some well-defined state.&lt;/p&gt;
&lt;p&gt;Ironically, error cleanup is one of the most error-prone parts of Zig programs and is a consistent source of resource leaks and memory corruption.&lt;/p&gt;
&lt;p&gt;This is easily understood: error codepaths are typically much less frequently executed and triggering them in tests can be difficult. As a result, they usually are only cognitively reviewed once or twice during development, and never truly exercised until a user hits them in production.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Errors in Zig&lt;/h2&gt;
&lt;p&gt;This section covers some background on how errors work in general in Zig. If you&#39;re familiar with Zig error handling, feel free to skip this section.&lt;/p&gt;
&lt;p&gt;All functions in Zig can return &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#Errors&quot;&gt;error values&lt;/a&gt;. An error value acts a bit like an enum and looks like this: &lt;code&gt;error.OutOfMemory&lt;/code&gt;. Error values are collected into name sets known as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#Error-Set-Type&quot;&gt;error sets&lt;/a&gt;. And functions can return either an error or a success value using something called an &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#Error-Union-Type&quot;&gt;error union&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can wrap an error union (usually a function call result) with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#try&quot;&gt;&lt;code&gt;try&lt;/code&gt;&lt;/a&gt; to unwrap the value or return the error.&lt;/p&gt;
&lt;p&gt;And, finally, the key point for this post, you can put &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#errdefer&quot;&gt;&lt;code&gt;errdefer&lt;/code&gt;&lt;/a&gt; anywhere and when an error is returned (either directly or via &lt;code&gt;try&lt;/code&gt;), then all prior &lt;code&gt;errdefer&lt;/code&gt; statements in the current scope will be executed in reverse order.&lt;/p&gt;
&lt;p&gt;All-in-all, it looks a bit like this in a toy example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;JavaScript&quot;&gt;&lt;span class=&quot;syntax-source syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-function-call syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;alloc&lt;/span&gt;: &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;Allocator&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-logical syntax-js&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-arithmetic syntax-js&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-block syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-storage syntax-type syntax-js&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-language syntax-self syntax-js&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;Widget&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;errdefer&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-language syntax-self syntax-js&quot;&gt;self&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;syntax-variable syntax-language syntax-self syntax-js&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-keyword syntax-operator syntax-arithmetic syntax-js&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;alloc&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;errdefer&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-language syntax-self syntax-js&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;syntax-storage syntax-type syntax-js&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;syntax-keyword syntax-operator syntax-assignment syntax-js&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-readwrite syntax-js&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-other syntax-object syntax-js&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property syntax-object syntax-js&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-method syntax-js&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-js&quot;&gt;openFile&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-js&quot;&gt;&amp;quot;&lt;/span&gt;config.txt&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-js&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-comma syntax-js&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;syntax-punctuation syntax-accessor syntax-js&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-object-literal syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-group syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-group syntax-js&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;syntax-comment syntax-line syntax-double-slash syntax-js&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-comment syntax-js&quot;&gt;//&lt;/span&gt; etc...
&lt;/span&gt;
    &lt;span class=&quot;syntax-keyword syntax-control syntax-flow syntax-js&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;syntax-variable syntax-language syntax-self syntax-js&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-terminator syntax-statement syntax-js&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;syntax-punctuation syntax-section syntax-block syntax-js&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, you can see the basic path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After allocating room for &lt;code&gt;Widget&lt;/code&gt;, if we see an error we should deallocate.&lt;/li&gt;
&lt;li&gt;After initializing the &lt;code&gt;Widget&lt;/code&gt;, if we see an error we should deinitialize it.&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a quintessential Zig pattern, and you will see it all over the Zig standard library and in most Zig programs. This is an easy case where &lt;code&gt;errdefer&lt;/code&gt; is &lt;em&gt;obviously correct&lt;/em&gt; even without tests.&lt;/p&gt;
&lt;p&gt;But the real world gets messy fast.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/tripwire#user-content-fn-2&quot;&gt;2&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why So Fragile?&lt;/h2&gt;
&lt;p&gt;It is also useful background to understand why it is so difficult to test error handling codepaths in Zig.&lt;/p&gt;
&lt;p&gt;Zig provides some great utilities for program correctness. First, it has a number of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#Illegal-Behavior&quot;&gt;runtime safety checks&lt;/a&gt; covering things from index of bounds to null dereferencing and so on. Next, Zig&#39;s &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#Memory&quot;&gt;parameterized allocators&lt;/a&gt; make it easier to test out of memory scenarios, constrained memory environments, and so on. Finally, Zig has a builtin &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/documentation/0.15.2/#Zig-Test&quot;&gt;test framework&lt;/a&gt; and encourages writing tests culturally.&lt;/p&gt;
&lt;p&gt;But, it has no mechanism to trigger errors to test &lt;code&gt;errdefer&lt;/code&gt;. For memory allocation errors, you can use specialized allocators like the &lt;code&gt;std.testing.failing_allocator&lt;/code&gt;, but getting it exactly configured right in complex code that does many allocations and whose allocations may be conditional is tricky and fragile.&lt;/p&gt;
&lt;p&gt;You always get &lt;code&gt;defer&lt;/code&gt; testing for free if you unit test your code because defers run unconditionally on block exit. But &lt;code&gt;errdefer&lt;/code&gt; only runs when errors occur, so if you can&#39;t trigger the error, you can&#39;t test the corresponding &lt;code&gt;errdefer&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;And besides tooling, error handling code by its nature is more rarely executed and the state of the world when an error occurs is often more complex than the success path (since it is also may be dependent on &lt;em&gt;where&lt;/em&gt; the error occurred).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Tripwire&lt;/h2&gt;
&lt;p&gt;I ultimately grew tired of eyeballing error handling code and hoping it was correct, or spending hours trying to write tests that create a perfect-but-fragile scenario to trigger an exact error path. So, I wrote &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/src/tripwire.zig&quot;&gt;Tripwire&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tripwire is a small, single-file library &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/tripwire#user-content-fn-1&quot;&gt;1&lt;/a&gt; that lets you put named points in your code where you can trigger errors during tests. Outside of tests, it is written in such a way that it is completely optimized away (takes no memory and emits no machine code).&lt;/p&gt;
&lt;p&gt;Conceptually it works like this:&lt;/p&gt;
&lt;p&gt;How tripwire injects failures&lt;/p&gt;
&lt;p&gt;Click to trigger:&lt;/p&gt;
&lt;p&gt;fn init(alloc: Allocator) !*Self {&lt;/p&gt;
&lt;p&gt;try tw.check(.alloc_buffer);&lt;/p&gt;
&lt;p&gt;const buf = try alloc.alloc(u8, 1024);&lt;/p&gt;
&lt;p&gt;errdefer alloc.free(buf);&lt;/p&gt;
&lt;p&gt;try tw.check(.open_file); ← error injected!&lt;/p&gt;
&lt;p&gt;const file = try openFile(&amp;quot;config&amp;quot;);&lt;/p&gt;
&lt;p&gt;errdefer file.close();&lt;/p&gt;
&lt;p&gt;return self;&lt;/p&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Triggering .open_file:&lt;/strong&gt; Buffer was allocated, so &lt;code&gt;errdefer alloc.free(buf)&lt;/code&gt; must run. If it&#39;s missing or wrong, the test fails with a memory leak!&lt;/p&gt;
&lt;p&gt;With code, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;const tripwire = @import(&amp;quot;tripwire.zig&amp;quot;);

// Define a tripwire module with named failure points. The second
// argument is the function itself to get its error set.
const init_tw = tripwire.module(enum {
    alloc_buffer,
    open_file,
}, init);

fn init(alloc: Allocator) !*Self {
    // Check the tripwire before the fallible operation.
    // In tests, this can be configured to return an error.
    // In release builds, this compiles to nothing.
    try init_tw.check(.alloc_buffer);
    const buf = try alloc.alloc(u8, 1024);
    errdefer alloc.free(buf);

    try init_tw.check(.open_file);
    const file = try std.fs.cwd().openFile(&amp;quot;config.txt&amp;quot;, .{});
    errdefer file.close();

    // ...
}

test &amp;quot;init error on open_file&amp;quot; {
    // Configure the tripwire to fail at the open_file point.
    try init_tw.errorAlways(.open_file, error.OutOfMemory);
    // Call the function and expect the error.
    try std.testing.expectError(error.OutOfMemory, init(std.testing.allocator));
    // End the tripwire session and reset state.
    // This also verifies the tripwire was actually hit.
    try init_tw.end(.reset);
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key insight is that &lt;code&gt;std.testing.allocator&lt;/code&gt; will fail the test if any memory is leaked. So by triggering an error at &lt;code&gt;.open_file&lt;/code&gt;, we force the &lt;code&gt;errdefer alloc.free(buf)&lt;/code&gt; to run. If that &lt;code&gt;errdefer&lt;/code&gt; was missing or wrong, the test would fail with a memory leak. In more complex scenarios, you&#39;d add additional state testing in the unit test after the error is tripped.&lt;/p&gt;
&lt;p&gt;Another common pattern I use is to iterate through all possible failure points to get complete coverage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;CSS&quot;&gt;&lt;span class=&quot;syntax-source syntax-css&quot;&gt;&lt;span class=&quot;syntax-meta syntax-selector syntax-css&quot;&gt;test &amp;quot;init handles all error points&amp;quot; &lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-property-list syntax-css&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;std&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;meta&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;tags&lt;/span&gt;(&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;init&lt;/span&gt;_&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;tw&lt;/span&gt;.F&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;ail&lt;/span&gt;P&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;oint&lt;/span&gt;)) |&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;point&lt;/span&gt;| {
        &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;init&lt;/span&gt;_&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;tw&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;error&lt;/span&gt;A&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;lways&lt;/span&gt;(&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;point&lt;/span&gt;, &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;error&lt;/span&gt;.O&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;ut&lt;/span&gt;O&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;f&lt;/span&gt;M&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;emory&lt;/span&gt;);
        &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;std&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;testing&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;expect&lt;/span&gt;E&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;rror&lt;/span&gt;(&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;error&lt;/span&gt;.O&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;ut&lt;/span&gt;O&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;f&lt;/span&gt;M&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;emory&lt;/span&gt;, &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;init&lt;/span&gt;(&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;std&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;testing&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;allocator&lt;/span&gt;));
        &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;init&lt;/span&gt;_&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;tw&lt;/span&gt;.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;end&lt;/span&gt;(.&lt;span class=&quot;syntax-meta syntax-property-name syntax-css&quot;&gt;reset&lt;/span&gt;);
&lt;/span&gt;    &lt;span class=&quot;syntax-punctuation syntax-section syntax-property-list syntax-css&quot;&gt;}&lt;/span&gt;
}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As I said before, in practice you probably need a bit more expectations after the tripwire end to verify that your world state is sensible. But even at a basic level, this makes it easy to ensure no runtime safety checks or leaks are detected.&lt;/p&gt;
&lt;p&gt;In addition to &lt;code&gt;errorAlways&lt;/code&gt;, you can use &lt;code&gt;errorAfter&lt;/code&gt; to only trigger an error after a certain number of times that that failure point has been reached. And &lt;code&gt;end&lt;/code&gt; will verify that it was triggered. This is useful to catch resource leakage or state corruption due to poor loop cleanup.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Zero-Cost Outside of Tests&lt;/h2&gt;
&lt;p&gt;Outside of tests, Tripwire emits no machine code and uses no memory; it is completely optimized away.&lt;/p&gt;
&lt;p&gt;To do this, we use Zig&#39;s comptime to detect the test framework:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;/// Whether our module is enabled or not.
pub const enabled = builtin.is_test;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And use that to conditionally make our functions no-ops:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;pub fn check(point: FailPoint) callconv(callingConvention()) Error!void {
  if (comptime !enabled) return;
  // actually do stuff
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also use comptime to set the calling convention so that the function is inlined if we&#39;re not enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;/// Our calling convention is inline if our tripwire module is
/// NOT enabled, so that all calls to `check` are optimized away.
fn callingConvention() std.builtin.CallingConvention {
    return if (!enabled) .@&amp;quot;inline&amp;quot; else .auto;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some have told me this isn&#39;t necessary, but we&#39;ve seen real compilations produce machine code with full function call overhead to a function with an empty body and only a &lt;code&gt;ret&lt;/code&gt;. Inlining fixes this, until we figure out what is causing that.&lt;/p&gt;
&lt;p&gt;And finally, the Zig compiler only analyzes and emits code for declarations that are actively referenced (or volatile). Since no code references this state when Tripwire is disabled, none of our global state gets emitted into the binary, either.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Bugs, Bugs&lt;/h2&gt;
&lt;p&gt;I integrated Tripwire into Ghostty in only a handful of places and immediately uncovered &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/10401&quot;&gt;many bugs&lt;/a&gt;. In the initial PR I fixed ~6 &lt;code&gt;errdefer&lt;/code&gt; bugs. They&#39;ve all never been known to trigger in the real world but they are bugs nonetheless.&lt;/p&gt;
&lt;p&gt;And most importantly, the bugs are now fixed paired with unit tests that verified they exist! If I remove the fix, the tests fail!&lt;/p&gt;
&lt;p&gt;I plan to continue to integrate Tripwire into more parts of the Ghostty codebase and to ensure that I&#39;m considering &lt;code&gt;errdefer&lt;/code&gt; testing for any newly written code by myself, maintainers, or contributors.&lt;/p&gt;
&lt;p&gt;Please, copy the file if you find it useful and use it in your own projects! Ghostty is MIT licensed and Tripwire is fully self-contained in a single file. Use it!&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It is a single file. Copy and paste it in your project if you want to use it. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/tripwire#user-content-fnref-1&quot;&gt;↩&lt;/a&gt; &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/tripwire#user-content-fnref-1-2&quot;&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I originally had a bunch of examples in this post but I think it made the post much too long and it&#39;s easier to just take a look at some of the commits in PR &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/8249&quot;&gt;8249&lt;/a&gt; and PR &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/10401&quot;&gt;10401&lt;/a&gt;. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/tripwire#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:60d2be2de980a3cfe7ca43de63c7503cc1f7d232</id>
    <title type="text">Finding and Fixing Ghostty&#39;s Largest Memory Leak</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-01-10-finding-and-fixing-ghostty-s-largest-memory-leak/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/ghostty-memory-leak-fix"/>
    <published>2026-01-10T00:00:00+00:00</published>
    <updated>2026-01-10T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">A few months ago, users started reporting that Ghostty was consuming absurd amounts of memory, with one user reporting 37 GB after 10 days of uptime. Today, I&#39;m happy to say the fix has been found and merged. This post is an overview of…</summary>
    <content type="html">&lt;p&gt;A few months ago, users started reporting that Ghostty was consuming absurd amounts of memory, with one user reporting &lt;strong&gt;37 GB&lt;/strong&gt; after 10 days of uptime. Today, I&#39;m happy to say &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/10251&quot;&gt;the fix has been found and merged&lt;/a&gt;. This post is an overview of what caused the leak, a look at some of Ghostty&#39;s internals, and some brief descriptions of how we tracked it down.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-memory-leak-fix#user-content-fn-2&quot;&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The leak was present since at least Ghostty 1.0, but it is only recently that popular CLI applications (particularly Claude Code) started producing the correct conditions to trigger it at scale. The limited conditions that triggered the leak are what made it particularly tricky to diagnose.&lt;/p&gt;
&lt;p&gt;The fix is merged and is available in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/install/pre&quot;&gt;tip/nightly releases&lt;/a&gt;, and will be part of the tagged 1.3 release in March.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The PageList&lt;/h2&gt;
&lt;p&gt;To understand the bug, we first need to understand how Ghostty manages terminal memory. Ghostty uses a data structure called the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/src/terminal/PageList.zig&quot;&gt;&lt;code&gt;PageList&lt;/code&gt;&lt;/a&gt; to store terminal content. PageList is a doubly-linked list of memory pages that store the terminal content (characters, styles, hyperlinks, etc.).&lt;/p&gt;
&lt;p&gt;PageList: A doubly-linked list of memory pages&lt;/p&gt;
&lt;p&gt;Page 1 oldest scrollback&lt;/p&gt;
&lt;p&gt;Page 2&lt;/p&gt;
&lt;p&gt;Page 3&lt;/p&gt;
&lt;p&gt;Page 4 newest active screen&lt;/p&gt;
&lt;p&gt;The underlying &amp;quot;pages&amp;quot; are not single &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/Page_(computer_memory)&quot;&gt;virtual memory pages&lt;/a&gt; but they are a contiguous block of memory aligned to page boundaries and composed of an even multiple of system pages.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-memory-leak-fix#user-content-fn-1&quot;&gt;2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These pages are allocated using &lt;code&gt;mmap&lt;/code&gt;. &lt;code&gt;mmap&lt;/code&gt; isn&#39;t particularly fast, so to avoid constant syscalls, we use a &lt;strong&gt;memory pool&lt;/strong&gt;. When we need a new page, we pull from the pool. When we&#39;re done with a page, we return it to the pool for reuse.&lt;/p&gt;
&lt;p&gt;The pool uses a &lt;strong&gt;standard size&lt;/strong&gt; for pages. Think of it like buying standard-sized shipping boxes: most things people ship fit in a standard box, and having a standard box comes with various efficiencies.&lt;/p&gt;
&lt;p&gt;But sometimes terminals need more memory than a standard page provides. If a set of lines has many emoji, styles, or hyperlinks, we need a larger page. In these cases, we allocate a &lt;strong&gt;non-standard page&lt;/strong&gt; directly with &lt;code&gt;mmap&lt;/code&gt;, bypassing the pool entirely. This is typically a rare scenario.&lt;/p&gt;
&lt;p&gt;Two types of page allocations&lt;/p&gt;
&lt;p&gt;Standard Pages (from pool)&lt;/p&gt;
&lt;p&gt;• Fixed size&lt;/p&gt;
&lt;p&gt;• Returned to pool when freed&lt;/p&gt;
&lt;p&gt;• Reusable for future allocations&lt;/p&gt;
&lt;p&gt;Non-Standard Pages (direct mmap)&lt;/p&gt;
&lt;p&gt;• Variable size (larger than standard)&lt;/p&gt;
&lt;p&gt;• Must call munmap to free&lt;/p&gt;
&lt;p&gt;• Cannot be reused&lt;/p&gt;
&lt;p&gt;When we &amp;quot;free&amp;quot; a page, we apply some simple logic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the page is &lt;code&gt;&amp;lt;= standard size&lt;/code&gt;: return it to the pool&lt;/li&gt;
&lt;li&gt;If the page is &lt;code&gt;&amp;gt; standard size&lt;/code&gt;: call &lt;code&gt;munmap&lt;/code&gt; to free it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the core background for terminal memory management in Ghostty, and the idea itself is sound. A logic bug around an optimization is what produced the leak, as we&#39;ll see next.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;There&#39;s one more background detail we need to cover to understand the bug: scrollback pruning.&lt;/p&gt;
&lt;p&gt;Ghostty has a &lt;code&gt;scrollback-limit&lt;/code&gt; configuration that caps how much history is retained. When you hit this limit, we delete the oldest pages in the scrollback buffer to free up memory.&lt;/p&gt;
&lt;p&gt;But this often happens in a super hot path (e.g. when outputting large amounts of data quickly), and allocating and freeing memory pages is expensive, even with the pool. Therefore, we have an optimization: &lt;strong&gt;reuse the oldest page as the newest page&lt;/strong&gt; when we reach the limit.&lt;/p&gt;
&lt;p&gt;Scrollback pruning: reusing the oldest page&lt;/p&gt;
&lt;p&gt;Before: at scrollback limit&lt;/p&gt;
&lt;p&gt;Page 1 to be pruned&lt;/p&gt;
&lt;p&gt;Page 2&lt;/p&gt;
&lt;p&gt;Page 3&lt;/p&gt;
&lt;p&gt;Page 4&lt;/p&gt;
&lt;p&gt;Remove from front, reuse at back&lt;/p&gt;
&lt;p&gt;After: page reused at end&lt;/p&gt;
&lt;p&gt;Page 2 now oldest&lt;/p&gt;
&lt;p&gt;Page 3&lt;/p&gt;
&lt;p&gt;Page 4&lt;/p&gt;
&lt;p&gt;Page 1 reused!&lt;/p&gt;
&lt;p&gt;This optimization works great. It requires zero allocations and uses only some quick pointer manipulations to move the page from the front to the back of the list. We do some metadata cleanup to &amp;quot;clear&amp;quot; the page but otherwise leave the previous memory intact.&lt;/p&gt;
&lt;p&gt;It&#39;s fast and empirically speeds up scrollback-heavy workloads significantly.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Bug&lt;/h2&gt;
&lt;p&gt;During the scrollback pruning optimization, we always &lt;strong&gt;resized our page back to standard size&lt;/strong&gt;. But we didn&#39;t resize the underlying memory allocation itself, we only noted the resize in the metadata. The underlying memory was still the large non-standard &lt;code&gt;mmap&lt;/code&gt; allocation, but now the PageList &lt;em&gt;thought&lt;/em&gt; it was standard sized.&lt;/p&gt;
&lt;p&gt;How metadata desync causes the leak&lt;/p&gt;
&lt;p&gt;1&lt;/p&gt;
&lt;p&gt;Allocate non-standard page&lt;/p&gt;
&lt;p&gt;metadata:&lt;/p&gt;
&lt;p&gt;2× std&lt;/p&gt;
&lt;p&gt;mmap:&lt;/p&gt;
&lt;p&gt;std&lt;/p&gt;
&lt;p&gt;+extra&lt;/p&gt;
&lt;p&gt;2&lt;/p&gt;
&lt;p&gt;Scrollback prunes &amp;amp; reuses&lt;/p&gt;
&lt;p&gt;metadata:&lt;/p&gt;
&lt;p&gt;std_size&lt;/p&gt;
&lt;p&gt;mmap:&lt;/p&gt;
&lt;p&gt;std&lt;/p&gt;
&lt;p&gt;+extra&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BUG:&lt;/strong&gt; metadata reset to std_size, but mmap unchanged!&lt;/p&gt;
&lt;p&gt;3&lt;/p&gt;
&lt;p&gt;Free the page&lt;/p&gt;
&lt;p&gt;metadata:&lt;/p&gt;
&lt;p&gt;std_size&lt;/p&gt;
&lt;p&gt;mmap:&lt;/p&gt;
&lt;p&gt;LEAKED&lt;/p&gt;
&lt;p&gt;std_size, assume pooled. &lt;strong&gt;munmap never called!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Standard Non-standard Leaked&lt;/p&gt;
&lt;p&gt;Eventually, we&#39;d free the page under various circumstances (e.g. when the user closes the terminal, but also other times). At that point, we&#39;d see the page memory was within the standard size, assume it was part of the pool, and we would &lt;em&gt;never call &lt;code&gt;munmap&lt;/code&gt;&lt;/em&gt; on it. A classic leak.&lt;/p&gt;
&lt;p&gt;This all seems pretty obvious, but the issue is that non-standard pages are rare by design. The goal of our design and optimizations is that standard pages are the common case and provide a fast-path. Only very specific scenarios produce non-standard pages and they&#39;re usually not produced in large quantities.&lt;/p&gt;
&lt;p&gt;But the rise of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://claude.com/product/claude-code&quot;&gt;Claude Code&lt;/a&gt; changed this. For some reason, Claude Code&#39;s CLI produces a lot of multi-codepoint grapheme outputs which force Ghostty to regularly use non-standard pages. Additionally, Claude Code uses the primary screen and produces a significant amount of scrollback output. These things combined together created the perfect storm to trigger the leak in huge quantities.&lt;/p&gt;
&lt;p&gt;I want to be explicit that this bug is not Claude Code&#39;s fault. Claude Code is simply exercising Ghostty in a way that exposes this long-standing bug.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;The fix is conceptually simple: &lt;strong&gt;never reuse non-standard pages&lt;/strong&gt;. If we encounter a non-standard page during scrollback pruning, we destroy it properly (calling &lt;code&gt;munmap&lt;/code&gt;) and allocate a fresh standard-sized page from the pool.&lt;/p&gt;
&lt;p&gt;The core of the fix is in the snippet below, but some extra work was needed to fix up some other bits of accounting we have:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;if (first.data.memory.len &amp;gt; std_size) {
    self.destroyNode(first);
    break :prune;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We could&#39;ve also reused the non-standard page and just retained the large memory size, but until we have data that shows otherwise, we&#39;re still operating under the assumption that standard pages are the common case and it makes sense to reset back to a standard pooled page.&lt;/p&gt;
&lt;p&gt;Other users have recommended more complex strategies (e.g. maintaining some metrics on how often non-standard pages are used and adjusting our assumptions accordingly), but more research is needed before making those changes. This change is simple, fixes the bug, and aligns with our current assumptions.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As part of the fix, I added support for virtual memory tags on macOS provided by the Mach kernel. This lets us tag our PageList memory allocations with a specific identifier that shows up in various tooling.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;inline fn pageAllocator() Allocator {
    // In tests we use our testing allocator so we can detect leaks.
    if (builtin.is_test) return std.testing.allocator;

    // On non-macOS we use our standard Zig page allocator.
    if (!builtin.target.os.tag.isDarwin()) return std.heap.page_allocator;

    // On macOS we want to tag our memory so we can assign it to our
    // core terminal usage.
    const mach = @import(&amp;quot;../os/mach.zig&amp;quot;);
    return mach.taggedPageAllocator(.application_specific_1);
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when debugging memory on macOS, Ghostty&#39;s PageList memory shows up with a specific tag instead of being lumped in with everything else. This made it trivial to identify the leak, associate it with the PageList, and also verify that the fix worked by observing the tagged memory being properly freed.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Preventing Leaks in Ghostty&lt;/h2&gt;
&lt;p&gt;We do a lot of work in the Ghostty project to find and prevent memory leaks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In debug builds and unit tests, we use leak-detecting Zig allocators.&lt;/li&gt;
&lt;li&gt;The CI runs &lt;code&gt;valgrind&lt;/code&gt; on our full unit test suite on every commit to find more than just leaks, such as undefined memory usage.&lt;/li&gt;
&lt;li&gt;We regularly run the macOS GUI via macOS Instruments to look for leaks particularly in the Swift codebase.&lt;/li&gt;
&lt;li&gt;We run every GTK-related PR using Valgrind (the full GUI) to look for leaks in the GTK codepath that isn&#39;t unit tested.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This has worked really well to date, but unfortunately it didn&#39;t catch this particular leak because it only triggers under very specific conditions that our tests didn&#39;t reproduce. The merged PR includes a test that does reproduce the leak to prevent regressions in the future.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was the largest known memory leak in Ghostty to date, and the only reported leak that has been confirmed by more than a single user. We&#39;ll continue to monitor and address memory reports as they come in, but remember that reproduction is the key to diagnosing and fixing memory leaks!&lt;/p&gt;
&lt;p&gt;Big thanks to &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/grishy&quot;&gt;@grishy&lt;/a&gt; who finally got me a reliable reproduction so I could analyze the issue myself. Their own analysis reached the same conclusion as mine, and the reproduction let me verify both our understandings independently.&lt;/p&gt;
&lt;p&gt;Thanks also to everyone who reported this issue with detailed diagnostics. The community&#39;s analysis, especially around the &lt;code&gt;footprint&lt;/code&gt; output and VM region counting, gave me important clues that pointed toward the PageList as the culprit.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;This post was written without the use of AI. AI was used to assist in some of the diagrams, but they were all reviewed for correctness by a human. None of the text content was AI-generated. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-memory-leak-fix#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The reason for this is not important for this blog post, but it is an interesting detail on its own. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-memory-leak-fix#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:6813f7d96988368b28664fd8a3ee33c3f7bbda1e</id>
    <title type="text">Ghostty Is Now Non-Profit</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-12-03-ghostty-is-now-non-profit/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/ghostty-non-profit"/>
    <published>2025-12-03T00:00:00+00:00</published>
    <updated>2025-12-03T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">Ghostty is now fiscally sponsored by Hack Club, a registered 501(c)(3) non-profit. Fiscal sponsorship is a legal and financial arrangement in which a recognized non-profit extends its tax-exempt status to a project that aligns with its…</summary>
    <content type="html">&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org&quot;&gt;Ghostty&lt;/a&gt; is now &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/sponsor#relationship-to-hack-club&quot;&gt;fiscally sponsored&lt;/a&gt; by &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://hackclub.com&quot;&gt;Hack Club&lt;/a&gt;, a registered 501(c)(3) non-profit.&lt;/p&gt;
&lt;p&gt;Fiscal sponsorship is a legal and financial arrangement in which a recognized non-profit extends its tax-exempt status to a project that aligns with its mission. This allows Ghostty to operate as a charitable initiative while Hack Club manages compliance, donations, accounting, and governance oversight.&lt;/p&gt;
&lt;p&gt;Being non-profit clearly demonstrates our commitment to keeping Ghostty free and open source for everyone. It paves the way for a model for sustainable development beyond my personal involvement. And it also provides important legal protections and assurances to the people and communities that adopt and use Ghostty.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why a Non-Profit?&lt;/h2&gt;
&lt;p&gt;Since the beginning of the project in 2023 and the private beta days of Ghostty, I&#39;ve repeatedly expressed my intention that Ghostty legally become a non-profit. This intention stems from several core beliefs I have.&lt;/p&gt;
&lt;p&gt;First, I want to lay bricks for a sustainable future for Ghostty that doesn&#39;t depend on my personal involvement technically or financially. Financially, I am still the largest donor to the project, and I intend to remain so, but a non-profit structure allows others to contribute financially without fear of misappropriation or misuse of funds (as protected by legal requirements and oversight from the fiscal sponsor).&lt;/p&gt;
&lt;p&gt;Second, I want to squelch any possible concerns about a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/Exit_scam&quot;&gt;&amp;quot;rug pull&amp;quot;&lt;/a&gt;. A non-profit structure provides enforceable assurances: the mission cannot be quietly changed, funds cannot be diverted to private benefit, and the project cannot be sold off or repurposed for commercial gain. The structure legally binds Ghostty to the public-benefit purpose it was created to serve.&lt;/p&gt;
&lt;p&gt;Finally, despite being decades-old technology, terminals and terminal-related technologies remain foundational to modern computing and software infrastructure. They&#39;re often out of the limelight, but they&#39;re ever present on developer machines, embedded in IDEs, visible as read-only consoles for continuous integration and cloud services, and still one of the primary ways remote access is done on servers around the world.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I believe infrastructure of this kind should be stewarded by a mission-driven, non-commercial entity that prioritizes public benefit over private profit.&lt;/strong&gt; That structure increases trust, encourages adoption, and creates the conditions for Ghostty to grow into a widely used and impactful piece of open-source infrastructure.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What This Means For Ghostty&lt;/h2&gt;
&lt;p&gt;From a technical perspective, nothing changes for Ghostty. Our technical goals for the project remain the same, the license (MIT) remains the same, and we continue our work towards better Ghostty GUI releases and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming&quot;&gt;libghostty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Financially, Ghostty can now &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://donate.ghostty.org&quot;&gt;accept tax-deductible donations&lt;/a&gt; in the United States. This opens up new avenues for funding the project and sustaining development over the long term. Most immediately, I&#39;m excited to begin &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/sponsor#contributor-compensation&quot;&gt;compensating contributors&lt;/a&gt;, but I also intend to support upstream dependencies, fund community events, and pay for boring operational costs.&lt;/p&gt;
&lt;p&gt;All our financial transactions will be transparent down to individual transactions for both inflows and outflows. You can view our public ledger at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://hcb.hackclub.com/ghostty&quot;&gt;Ghostty&#39;s page on Hack Club Bank&lt;/a&gt;. At the time of writing, this is empty, but you&#39;ll soon see some initial funding from me and the beginning of paying for some of our operational costs.&lt;/p&gt;
&lt;p&gt;All applicable names, marks, and intellectual property associated with Ghostty have been transferred to Hack Club and are now owned under the non-profit umbrella. Copyright continues to be held by individual contributors under the continued and existing license structure.&lt;/p&gt;
&lt;p&gt;From a leadership perspective, I remain the project lead and final authority on all decisions, but as stated earlier, the creation of a non-profit structure lays the groundwork for an eventual future beyond this model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important note: no funds will be sent to me (Mitchell Hashimoto) or used in any way that personally benefits me.&lt;/strong&gt; Since I&#39;m both the largest donor and lead of this project, this is a legally guaranteed protection. But also for altruistic reasons, all funds will be directed towards the needs of the project and its community.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Supporting Hack Club&lt;/h2&gt;
&lt;p&gt;As our fiscal sponsor, Hack Club provides essential services to Ghostty, including accounting, legal compliance, and governance oversight. To support this, 7% of all donations to Ghostty go to Hack Club to cover these costs in addition to supporting their broader mission of empowering young people around the world interested in technology and coding.&lt;/p&gt;
&lt;p&gt;In the words of Zach Latta, Hack Club&#39;s founder and executive director this is a &amp;quot;good-for-good&amp;quot; trade. Instead of donor fees going to a for-profit management company or covering pure overhead of a single project, the fees go to another non-profit doing important work in the tech community and the overhead is amortized across many projects.&lt;/p&gt;
&lt;p&gt;In addition to the 7% fees, &lt;strong&gt;my family is personally donating $150,000 directly to the Hack Club project&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-non-profit#user-content-fn-1&quot;&gt;1&lt;/a&gt; (not to Ghostty within it). Hack Club does amazing work and I would&#39;ve supported them regardless of their fiscal sponsorship of Ghostty, but I wanted to pair these two things together to amplify the impact of both.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Donate&lt;/h2&gt;
&lt;p&gt;Please consider donating to support Ghostty&#39;s continued development.&lt;/p&gt;
&lt;p&gt;I recognize that Ghostty is already in an abnormally fortunate position to have myself as a backer, but I do envision a future where Ghostty is more equally supported by a broader community. And with our new structure, you can be assured about the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/sponsor#usage-of-funds&quot;&gt;usage of your funds&lt;/a&gt; towards public-benefit goals.&lt;/p&gt;
&lt;p&gt;This post isn&#39;t meant to directly be a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits&quot;&gt;fundraising pitch&lt;/a&gt; so it is purposely lacking critical details about our funding goals, budget, project goals, project metrics, etc. I&#39;ll work on those in the future. In the mean time, if you&#39;re interested in talking more about supporting Ghostty, please email me at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;mailto:m@mitchellh.com&quot;&gt;m@mitchellh.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Your contribution helps sustain development and keeps Ghostty free and open source for everyone. Donations are tax-deductible in the United States.&lt;/p&gt;
&lt;p&gt;DAF or Foundation&lt;/p&gt;
&lt;p&gt;Use the EIN above and specify “Ghostty” as the recipient&lt;/p&gt;
&lt;p&gt;7% of donations go to Hack Club to cover administrative costs and support their mission.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Thank You&lt;/h2&gt;
&lt;p&gt;I&#39;m thankful for Hack Club and their team for working with us to make this happen. I&#39;m also thankful for the Ghostty community who has supported this project and has trusted me and continues to trust me to steward it responsibly.&lt;/p&gt;
&lt;p&gt;For more information about Ghostty&#39;s non-profit structure, see the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/sponsor&quot;&gt;dedicated page on Ghostty&#39;s website.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;We haven&#39;t finalized the transfer of the funds yet, but it is initiated and will be completed in the coming weeks. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-non-profit#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:2bb8e7b0fe9bd6824e419042252f926054eb0902</id>
    <title type="text">Vibing a Non-Trivial Ghostty Feature</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-10-11-vibing-a-non-trivial-ghostty-feature/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/non-trivial-vibing"/>
    <published>2025-10-11T00:00:00+00:00</published>
    <updated>2025-10-11T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">I recently shipped a non-trivial Ghostty feature (unobtrusive macOS automatic updates) that was largely developed with AI. I&#39;m regularly asked to share non-trivial examples of how I use AI and agentic coding tools and this felt like a…</summary>
    <content type="html">&lt;p&gt;I recently shipped a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/9116&quot;&gt;non-trivial Ghostty feature (unobtrusive macOS automatic updates)&lt;/a&gt; that was &lt;em&gt;largely&lt;/em&gt; developed with AI.&lt;/p&gt;
&lt;p&gt;I&#39;m regularly asked to share non-trivial examples of how I use AI and agentic coding tools and this felt like a golden opportunity to walk through my process with a well-scoped, non-trivial, real-world, shipping feature &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fn-6&quot;&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post will share every single agentic coding session I had on the path to shipping this feature, unedited and in full. Alongside it, I&#39;ll provide some additional context about my process and reasoning. And yes, I&#39;ll also share the token cost for those curious about that, too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important: there is a lot of human coding, too.&lt;/strong&gt; I almost always go in after an AI does work and iterate myself for awhile, too. Rather than say that at every turn, I&#39;m just saying it once here. Therefore, you may see some discrepancies between what the AI produced and what ended up in the final code. This is intentional and I believe good AI drivers are experts in their domains and utilize AI as an assistant, not a replacement.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Feature&lt;/h2&gt;
&lt;p&gt;The finished feature this blog post is about is the macOS unobtrusive update notification feature. This feature shows update status within the terminal window without interrupting work by creating windows, grabbing focus, etc.&lt;/p&gt;
&lt;p&gt;Let&#39;s set the stage for what lead to this feature (pun intended, as you&#39;ll see shortly). During a high-profile OpenAI keynote, a demo was rudely interrupted by a Ghostty update prompt:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;article-picture article-picture-fallback&quot; style=&quot;--image-ratio:16 / 9&quot;&gt;&lt;img src=&quot;https://mitchellh.com/_next/image?url=%2Fstatic%2Fimages%2Fopenai-demo.png&amp;amp;w=3840&amp;amp;q=75&quot; alt=&quot;OpenAI Demo&quot; loading=&quot;eager&quot; decoding=&quot;async&quot; fetchpriority=&quot;high&quot; referrerpolicy=&quot;no-referrer&quot; class=&quot;progressive-image&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I wanted to ensure that never happened again &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fn-2&quot;&gt;2&lt;/a&gt;. The path I decided to take was to make update notifications &lt;em&gt;unobtrusive&lt;/em&gt;. Instead of popping up a window, the app would instead show a small, non-modal GUI element somewhere that wouldn&#39;t interrupt the user.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Pre-AI Planning&lt;/h2&gt;
&lt;p&gt;So I pulled out my AI tooling. &lt;strong&gt;Absolutely not.&lt;/strong&gt; I began by coming up with a rough plan of how I wanted this to work. Ghostty uses &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://sparkle-project.org/&quot;&gt;Sparkle&lt;/a&gt;, an extremely popular macOS update framework. I poked around their docs and found that they support &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://sparkle-project.github.io/documentation/api-reference/&quot;&gt;custom UI through Obj-C protocols&lt;/a&gt;. You have to reimplement a ton of stuff from scratch, but it is possible.&lt;/p&gt;
&lt;p&gt;Okay, so I had a rough idea of &lt;em&gt;backend&lt;/em&gt;. For &lt;em&gt;frontend&lt;/em&gt;, I wasn&#39;t really sure (and it isn&#39;t my area of expertise). I had this really vague idea that it should be a little button embedded in the title bar, and I know macOS supports custom UI in the titlebar via &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/documentation/appkit/nswindow/addtitlebaraccessoryviewcontroller(_:)&quot;&gt;titlebar accessory controllers&lt;/a&gt;, but beyond that I didn&#39;t have much of a sense of how it should look or feel.&lt;/p&gt;
&lt;p&gt;But that&#39;s enough to get started. AI is a very good prototyper, so even knowing what you don&#39;t know is useful enough to get started. I had a strong enough sense of the big picture.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;First Session: Prototyping the UI&lt;/h2&gt;
&lt;p&gt;Here is my &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-9fc3eb88-5aa2-45e4-8f6d-03697f53102d&quot;&gt;first agentic coding session&lt;/a&gt;, with the starting prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I want to enable custom, unobtrusive update notifications and installs by customizing SPUUserDriver. Let&#39;s start by planning the custom UI we&#39;ll need. We&#39;ll ONLY work on UI. Create a plan for creating SwiftUI views that can show the various states that are required by SPUUserDriver. I think the best place for these to show up is in the macOS window titlebars on the top-right. Create a plan to put it there. Consult the oracle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Frequent question: &amp;quot;What is the oracle?&amp;quot; It is an &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/news/oracle&quot;&gt;Amp-specific read-only subagent&lt;/a&gt; that uses a slower, higher-cost model that is generally better at thinking about things. I consult the oracle for all planning.&lt;/p&gt;
&lt;p&gt;To start, I decided to prototype the UI first.&lt;/p&gt;
&lt;p&gt;Notice here that I don&#39;t send the agent off to &lt;em&gt;build the full feature&lt;/em&gt;. There are a couple reasons for this. First and foremost, I still don&#39;t even know what I want the UI/UX to be like, so I can&#39;t possibly coherently expect an AI to do that for me amongst other changes. Second, smaller chunks of work are easier to review, understand, and iterate on.&lt;/p&gt;
&lt;p&gt;Another thing to notice is that I ask it to only create a plan and not to write any code. Since this is a relatively vague request, it&#39;s important that I review a plan before it goes off and does a ton of work (and spends a ton of tokens doing it).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Creating a comprehensive plan interactively with an agent is a really important first-step for anything non-trivial. I usually also save it out (via the agent) to something like &lt;code&gt;spec.md&lt;/code&gt; and in future sessions I can say &amp;quot;Consult the @spec.md and work on &lt;em&gt;some task&lt;/em&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;The agent came up with an &lt;em&gt;agreeable enough&lt;/em&gt; plan that I let it go ahead and implement it. You can see the rest of my conversations as I iterate on that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The UI it made was directionally very good.&lt;/strong&gt; It had a &lt;em&gt;ton&lt;/em&gt; of polish issues (spacing, colors, etc.) but seeing the UI gave me the spark of inspiration I needed to know what I wanted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; I very often use AI for inspiration. In this case, I ended up keeping a lot (not all) of the UI code it made, but I will very often prompt an agent, throw away everything it did, and redo it myself (manually!). I find the &amp;quot;zero to one&amp;quot; stage of creation very difficult and time consuming and AI is excellent at being my muse.&lt;/p&gt;
&lt;h3&gt;Hitting a Wall&lt;/h3&gt;
&lt;p&gt;You can see in chats 11 to 14 that we&#39;re entering the &lt;em&gt;slop zone&lt;/em&gt;. The code the agent created has a critical bug, and it&#39;s absolutely failing to fix it. And I have no idea how to fix it, either.&lt;/p&gt;
&lt;p&gt;I&#39;ll often make these few hail mary attempts to fix a bug. If the agent can figure it out, I can study it and learn myself. If it doesn&#39;t, it costs me very little. If the agent figures it out and I don&#39;t understand it, I back it out. I&#39;m not shipping code I don&#39;t understand. While it&#39;s failing, I&#39;m also tabbed out searching the issue and trying to figure it out myself.&lt;/p&gt;
&lt;p&gt;It&#39;s at this point that I know I need to step back, review what it did, and come up with my own plans. It&#39;s time to educate myself and think critically. AI is no longer the solution; it is a liability.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Cleanup Sessions&lt;/h2&gt;
&lt;p&gt;I spend the next few sessions guiding the agent to clean up the code.&lt;/p&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-cad10729-a348-4dc6-9297-e1130a12f5da&quot;&gt;second session&lt;/a&gt; focused on moving some methods around into better places I recognized:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Let&#39;s move the pill background, foreground, and badge functions from @macos/Sources/Features/Update/UpdateAccessoryView.swift to @macos/Sources/Features/Update/UpdateViewModel.swift and make them more generic (background, foreground, badge)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-ae7a7144-6293-4bf6-af24-982b8ff09984&quot;&gt;third session&lt;/a&gt; added documentation to the code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update the docs for @UpdateBadge.swift&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Adding documentation is a really important step because it helps reaffirm your own understanding of the code as well as educate future agents that may read and modify this code. I find agents do much better when they have both natural language descriptions as well as the code itself.&lt;/p&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-af16fe85-bdc5-4fcb-b7e7-dbc425ca5da7&quot;&gt;fourth session&lt;/a&gt; moves the view model to an app-global location, since the original work put it in a window scope and update information is app scoped.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Move the update view model data to AppDelegate since the update information will be app-global.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Throughout these, I am usually dropping in and making minor manual changes as well.&lt;/p&gt;
&lt;p&gt;The cleanup step is really important. To cleanup effectively you have to have a pretty good understanding of the code, so this forces me to not blindly accept AI-written code. Subsequently, better organized and documented code helps future agentic sessions perform better.&lt;/p&gt;
&lt;p&gt;I sometimes tongue-in-cheek refer to this as the &amp;quot;anti-slop session&amp;quot;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Facing &amp;quot;The Bug&amp;quot;&lt;/h2&gt;
&lt;p&gt;Time to get back to that bug that I found in that initial session. I once again spent a few sessions trying to get the agent to figure this out. I start vague, and slowly get more specific with how I&#39;d approach it.&lt;/p&gt;
&lt;p&gt;First, the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-5b64618e-3884-4bf9-87cb-e041f755279f&quot;&gt;vague session&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For standard, native tabs, the update accesory view is not visible. It should remain visible in the titlebar of the window.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Failure. Then, I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-0230ecf6-4d77-48da-99e8-ffeb35d3163b&quot;&gt;get more specific&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We need to update @macos/Sources/Features/Terminal/Window Styles/TerminalTabsTitlebarTahoe.swift tab bar constraints to align the right edge of the tab bar with the left edge of the update accessory view so it remains visible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Failure. Then, I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-2b5aecbe-7a48-4a4e-b5ad-c355b751042b&quot;&gt;try a different, specific approach&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What if we changed the @macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift to make the tab bar a top accessory view rather than a bottom one to make our tabs go into the titlebar?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Failure. One &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-47dbc7c1-796a-45b4-ba21-3385cc89411a&quot;&gt;last attempt&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &amp;quot;right accessory view&amp;quot; and layout of @macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift is conflictig with the update accessory view setup in @macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift Can we constrain the tab bar to always appear left of the update notice?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Failure.&lt;/p&gt;
&lt;p&gt;This whole time, I&#39;ve also spent time trying to solve this myself through manual research and human effort. My more specific prompts are based on things I&#39;ve learned through that process. But overall, it was clearly not working.&lt;/p&gt;
&lt;p&gt;I don&#39;t think I can figure this out on my own, so I decide to pivot. I decided that for these problematic titlebar styles, let&#39;s put the update notices in the bottom-right corner of the window overlaid on top of the content view rather than in the titlebar.&lt;/p&gt;
&lt;p&gt;I need to support this &lt;em&gt;anyways&lt;/em&gt; because Ghostty has a configuration to hide the titlebar entirely. So, even if I can solve the titlebar styling issue later, I still need to support this other mode.&lt;/p&gt;
&lt;p&gt;My &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-69675325-c5e3-497a-b692-0176744069e9&quot;&gt;next session&lt;/a&gt; moves forward with this plan with a very specific prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Augment the @macos/Sources/Features/Update system to also support an overlay approach in @macos/Sources/Features/Terminal/TerminalView.swift The update notice should appear at the bottom of the window. It should go over the text (so it doesn&#39;t resize the terminal view). All clicking behaviors should otherwise be the same as the accessory view.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It did a really good job with this. I did a lot of manual polish after (moving stuff around, renaming things, etc.) but the core work was solid.&lt;/p&gt;
&lt;p&gt;Here is a video of the feature shortly after this session, showcasing how the update notice appears in the bottom-right corner of the window for certain titlebar styles or when the titlebar is hidden:&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Starting the Backend&lt;/h2&gt;
&lt;p&gt;The UI was feeling &lt;em&gt;good enough&lt;/em&gt;. I had noted a bunch of polish issues I wanted to tackle later but I wanted to move on to the backend work primarily to see if I&#39;d discover any unknown unknowns that threw a wrench in my plans.&lt;/p&gt;
&lt;p&gt;I manually created a file with a scaffold of incomplete functions and various &lt;code&gt;TODO&lt;/code&gt; comments. I then started &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-d812d8a2-b7af-47ca-a91b-5ce3a18068b2&quot;&gt;a session to complete that work for me&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Complete the @macos/Sources/Features/Update/UpdateDriver.swift Read the Sparkle documentaiton as necessary to understand the functionality. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://sparkle-project.org/documentation/api-reference/Protocols.html&quot;&gt;https://sparkle-project.org/documentation/api-reference/Protocols.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; AI is very good at fill-in-the-blank or draw-the-rest-of-the-owl. My pattern here of creating scaffolding with descriptive function names, parameters, todo comments, etc. is a really common one for me and it works very well.&lt;/p&gt;
&lt;p&gt;It actually did a &lt;strong&gt;really bad job here&lt;/strong&gt; and I ended up throwing all of this code away. The code it produced &lt;em&gt;worked&lt;/em&gt;, but it was obviously the wrong approach. It conflated a lot of different concerns and the way it was storing state in the driver was clearly wrong.&lt;/p&gt;
&lt;p&gt;When I studied what it did, I realized it was because the view model was structured in a suboptimal way, so I switched gears into cleanup mode to give AI (and humans, if I chose to write it myself) the better framework to work with.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Cleanup Again, Big Time&lt;/h2&gt;
&lt;p&gt;Experience has taught me that the cleanliness of a UI frontend and business logic backend is often dictated by the quality of the view model in between. So I spent some time manually restructured the view model. This involved switching to a tagged union rather than the struct with a bunch of optionals. I renamed some types, moved stuff around.&lt;/p&gt;
&lt;p&gt;I knew from experience that this small bit of manual work in the middle would set the agents up for success in future sessions for both the frontend and backend. After completing it, I continued with a marathon set of cleanup sessions.&lt;/p&gt;
&lt;p&gt;After doing the restructuring, the first thing I did was &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-29c43cfa-5b00-420d-9490-ce16cea29103&quot;&gt;ask the agent to once again complete the owl&lt;/a&gt;, this time inspecting my changes and updating dependent code to the new style and removing the old:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update @macos/Sources/Features/Update/UpdateViewModel.swift to use the new &lt;code&gt;UpdateState&lt;/code&gt; exclusively. Rename &lt;code&gt;state2&lt;/code&gt; to &lt;code&gt;state&lt;/code&gt; (remove the old state).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-5c8f7208-fcad-4f62-a212-65c7c2d61346&quot;&gt;asked it remove additional dead code&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I think we can get rid of UpdateUIActions. They&#39;re not used anymore since our UpdateState has callbacks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then I broke the build myself cleaning up some things. I had a meeting to hop onto so I decided to &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-214e0715-3fbb-42ee-9ad7-bc3ded364f36&quot;&gt;let the agent fix it while I was busy&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Run the build and fix the errors&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &amp;quot;I broke a bunch of things, please fix my mess.&amp;quot; is another frequent use case I have for agents. I&#39;d say generally this fits within the same fill-in-the-blank pattern as before.&lt;/p&gt;
&lt;p&gt;Later, I started doing some &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-3ff3649d-4610-4bbe-8c2a-7875b03e9a5d&quot;&gt;refactors on some of the views again&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Turn each @macos/Sources/Features/Update/UpdatePopoverView.swift case into a dedicated fileprivate Swift view that takes the typed value as its parameter so that we can remove the guards.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-0477a9aa-e1ff-4df0-bf16-eed5131b6c14&quot;&gt;More cleanup&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Change &lt;code&gt;iconName&lt;/code&gt; in @macos/Sources/Features/Update/UpdateViewModel.swift to be an optional, return nil for blank. Update usage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Simulation&lt;/h2&gt;
&lt;p&gt;In my first UI session, I had the agent create some demo code so I could see the UI in action without real update checks. But update flows have a number of scenarios and up to this point I only tested the happy path.&lt;/p&gt;
&lt;p&gt;In my &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-a2faf4af-c67b-47a5-99d1-b1a0f957cafb&quot;&gt;next session&lt;/a&gt; I extracted the simulation code into a dedicated file and asked the agent to create more scenarios:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Extract the update simulation code in @macos/Sources/App/macOS/AppDelegate.swift into a dedicated file in @macos/Sources/Features/Update . This should contain multiple simulation scenarios (happy path, not found, errors, etc.) so that we can easily try different demos.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Agents are great at generating tests and simulations. The simulation code specifically that it generated here is honestly pretty gross but it works and it&#39;s not part of the release binary so the quality doesn&#39;t matter to me. I didn&#39;t even bother cleaning it up beyond the basics you can see in the session.&lt;/p&gt;
&lt;p&gt;I then ran the various simulations and found a number of UX improvements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-01583a65-e9c8-457d-bf27-603b0297bb8d&quot;&gt;Cleaning up the &amp;quot;No Update Found&amp;quot; state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-e5a5cd11-c024-4562-8afc-8d1574b32a87&quot;&gt;Reset state on confirming error&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-f41a9020-e665-4d67-aa7a-d2e96124ad8c&quot;&gt;Copy and style changes for permission request state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Last Mile&lt;/h2&gt;
&lt;p&gt;At this point, I had a working backend and frontend and I needed to hook it all up.&lt;/p&gt;
&lt;p&gt;My &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-6968a022-f1a2-4011-806a-1060e8e8fd81&quot;&gt;next session&lt;/a&gt; prompted the agent to do this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Make an &lt;code&gt;UpdateController&lt;/code&gt; class the same as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/sparkle-project/Sparkle/blob/2.x/Sparkle/SPUStandardUpdaterController.m&quot;&gt;https://github.com/sparkle-project/Sparkle/blob/2.x/Sparkle/SPUStandardUpdaterController.m&lt;/a&gt; but for our updater types.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This required some back and forth and manual polish, but it got there.&lt;/p&gt;
&lt;p&gt;I then &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-fce4f9e0-7bb4-4930-b2b1-34fd600bc242&quot;&gt;made some minor improvements&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For our update available state with the appcast, look at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://sparkle-project.org/documentation/api-reference/Classes/SUAppcastItem.html&quot;&gt;https://sparkle-project.org/documentation/api-reference/Classes/SUAppcastItem.html&lt;/a&gt; and show other relevant metadata if its set. For example, content length for size.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Anything Else?&lt;/h2&gt;
&lt;p&gt;My &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ampcode.com/threads/T-e4cc70f0-d222-4c40-a1e7-745025e3dc9c&quot;&gt;last prompt to an agent&lt;/a&gt; is always to ask what else I might be missing. &lt;strong&gt;I do this regardless of if I manually wrote the code myself or not.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Are there any other improvements you can see to be made with the @macos/Sources/Features/Update feature? Don&#39;t write any code. Consult the oracle. Consider parts of the code that can also get more unit tests added.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This highlighted some real issues, so I went ahead and asked it to implement them. I find its easier to tell the agent &amp;quot;okay just do it all&amp;quot; rather than ask it specific things to do, since I can always easily clean it up in selective commits later.&lt;/p&gt;
&lt;p&gt;An amusing thing in this session is that the agent started going down a really crazy rabbit hole and so I stepped in to stop it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stop stop stop. Undo all the main actor stuff.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I also noticed that it did something rather poorly when there is a better way:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For the error message, instead of truncating, isn&#39;t there a SwiftUI standard way to do this? We should add an additional UI element they can use to view the whole message.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Cost and Time&lt;/h2&gt;
&lt;p&gt;The work took a total of &lt;strong&gt;16 separate sessions&lt;/strong&gt; totalling &lt;strong&gt;$15.98&lt;/strong&gt; in token spend on Amp &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fn-4&quot;&gt;3&lt;/a&gt;. I won&#39;t try to speculate whether this is expensive or cheap in general, but I will say for me personally I spent more than that in coffee shops in the two calendar days I spent on this feature.&lt;/p&gt;
&lt;p&gt;The total &amp;quot;wall clock&amp;quot; time I spent on this feature I estimate around 8 hours. I only spend around 4 hours a day on the computer the first and last commit span 3 days. But I also didn&#39;t spend all of my time on this feature. For example, I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/install/release-notes/1-2-2&quot;&gt;shipped a Ghostty update&lt;/a&gt;, was a guest on &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.twitch.tv/theprimeagen&quot;&gt;ThePrimeagen&lt;/a&gt; for an hour, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://x.com/adam_chal/status/1976527358099337325&quot;&gt;gave a guest presentation at Zoo&#39;s annual all-hands&lt;/a&gt;, all during the hours I have available for &amp;quot;computer work&amp;quot; during the same days I was working on this feature &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fn-5&quot;&gt;4&lt;/a&gt;. So, I think the 8 hour estimate is generous.&lt;/p&gt;
&lt;p&gt;Many people on the internet argue whether AI enables you to work faster or not. In this case, I think I shipped this faster than I would have if I had done it all myself, in particular because iterating on minor SwiftUI styling is so tedious and time consuming for me personally and AI does it so well.&lt;/p&gt;
&lt;p&gt;I think the faster/slower argument for me personally is missing the thing I like the most: the AI can work for me while I step away to do other things. Here is a picture I took of one of my cleanup sessions while I was making breakfast for my family:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;article-picture article-picture-fallback&quot; style=&quot;--image-ratio:16 / 9&quot;&gt;&lt;img src=&quot;https://mitchellh.com/_next/image?url=%2Fstatic%2Fimages%2Fvibing-breakfast.jpeg&amp;amp;w=3840&amp;amp;q=75&quot; alt=&quot;Vibing and Cooking&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; referrerpolicy=&quot;no-referrer&quot; class=&quot;progressive-image&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;There&#39;s all sorts of dismissals about this, such as &amp;quot;I don&#39;t want to be coding while I&#39;m cooking&amp;quot; or &amp;quot;Be more present&amp;quot; or whatever. If that&#39;s how you want to be, that&#39;s fine. In my case, in this specific example, I&#39;m the first person awake in my household and I prep breakfast while everyone else is still asleep. I don&#39;t do this every waking moment.&lt;/p&gt;
&lt;p&gt;All this is to say: it works for me. I&#39;m really, really not trying to &lt;em&gt;convince you&lt;/em&gt;. I&#39;m not associated financially with any AI company. But as someone who has a lot of success with AI tooling and likes to talk about it, people ask me to share examples all the time. That&#39;s what I&#39;m doing here.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;End&lt;/h2&gt;
&lt;p&gt;I think the feature is beautiful, works great, and after a final manual review I merged it &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fn-7&quot;&gt;5&lt;/a&gt;. For Ghostty users who are on the tip release, it is available now. For Ghostty users who are on tagged releases, this feature will be available in Ghostty 1.3.&lt;/p&gt;
&lt;p&gt;I&#39;m an outspoken advocate about the importance of sharing agentic coding sessions publicly &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fn-1&quot;&gt;6&lt;/a&gt;, with one of the reasons being that it&#39;s an incredibly powerful way to educate others about how to use these tools effectively. I hope this post helps demonstrate that.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It is currently only shipped to nightly beta testers (&amp;quot;tip&amp;quot; testers) but that is a group of thousands of people. It is merged and will be in the next stable Ghostty release. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fnref-6&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Yes, it was good marketing. No, it was not on purpose. And no, I&#39;m not celebrating it, because you don&#39;t want users to a fear a tool will take advantage of them. You want to trust that it&#39;s there to help. I want presenters (or anyone, really) to &lt;em&gt;want&lt;/em&gt; to use Ghostty, and caring about stuff like this is part of that. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The most poetic though would&#39;ve been to use OpenAI Codex for this. I&#39;m sure it would&#39;ve done great! This post isn&#39;t an advertisement for Amp, it just happens to be the agentic coding tool I use the most currently. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I have a toddler at home so my &amp;quot;computer time&amp;quot; is very scheduled and very limited. 😁 &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fnref-5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;quot;Final manual review&amp;quot; is &lt;em&gt;super super super important&lt;/em&gt;. This probably shouldn&#39;t be a footnote, but I couldn&#39;t find a better place to emphasize it. Please don&#39;t ever ship AI-written code without a thorough manual review. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fnref-7&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My reasoning for this could also be its own blog post, so I&#39;m going to avoid explaining myself further here beyond what I already have. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/non-trivial-vibing#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:0e4fd234cd8afeff252ff11086ec6c1b8d92965f</id>
    <title type="text">Zig Builds Are Getting Faster</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-10-03-zig-builds-are-getting-faster/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/zig-builds-getting-faster"/>
    <published>2025-10-03T00:00:00+00:00</published>
    <updated>2025-10-03T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">Andrew Kelley famously (or infamously, depending on your views) said &quot;the compiler is too damn slow, that&#39;s why we have bugs.&quot;1 As a result, one of the primary stated goals of Zig for years has been faster compile times. The Zig team has…</summary>
    <content type="html">&lt;p&gt;Andrew Kelley famously (or infamously, depending on your views) said &amp;quot;the compiler is too damn slow, that&#39;s why we have bugs.&amp;quot;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-builds-getting-faster#user-content-fn-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As a result, one of the primary stated goals of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org&quot;&gt;Zig&lt;/a&gt; for years has been faster compile times. The Zig team has been working on extremely hard problems to make this a reality (such as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ziglang/zig/issues/16270&quot;&gt;yeeting LLVM&lt;/a&gt;, writing their own &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/download/0.15.1/release-notes.html#Compiler&quot;&gt;code generation backends&lt;/a&gt;, building their own &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ziglang/zig/pull/25299&quot;&gt;linkers&lt;/a&gt;, and marching towards &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org/download/0.15.1/release-notes.html#Incremental-Compilation&quot;&gt;incremental compilation&lt;/a&gt; in general).&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-builds-getting-faster#user-content-fn-2&quot;&gt;2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The fruits of this multi-year labor are finally starting to show with Zig 0.15.1. The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org&quot;&gt;Ghostty&lt;/a&gt; project just completed upgrading to Zig 0.15.1, and I&#39;d like to share some real-world build times.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-builds-getting-faster#user-content-fn-3&quot;&gt;3&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Build Script Compilation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Zig 0.14: 7sec 167ms&lt;/li&gt;
&lt;li&gt;Zig 0.15: 1sec 702ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the time it takes to build the &lt;code&gt;build.zig&lt;/code&gt; script itself. The times above were measured by running &lt;code&gt;zig build --help&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A well-written build script should only rebuild itself rarely. However, this is a cost every new uncached source build will pay (e.g. a user downloading the project to build from source one time). As such, it directly impacts the time to build a usable binary.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Full Uncached Ghostty Binary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Zig 0.14: 41sec&lt;/li&gt;
&lt;li&gt;Zig 0.15: 32sec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This includes the time to build the build script itself. Given the prior results, Zig 0.15 is building everything else ~2 seconds faster. But, you can still see in wall time the change in this initial build time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important: most of this is still using LLVM.&lt;/strong&gt; Ghostty still can&#39;t fully build and link using the self-hosted x86_64 backend, since the backend still has bugs. So, this just shows the general improvements in the Zig compiler itself, even with LLVM in the picture.&lt;/p&gt;
&lt;p&gt;Once Ghostty can use the self-hosted x86_64 backend completely, I expect this time to plummet to around 25 seconds or less, fully half the time it would take with Zig 0.14.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Incremental Build (Ghostty Executable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Zig 0.14: 19sec&lt;/li&gt;
&lt;li&gt;Zig 0.15: 16sec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the time it takes to rebuild Ghostty after a one-line change to the most core terminal emulation code (adding a log function call to the escape sequence parser).&lt;/p&gt;
&lt;p&gt;This build has a fully cached build script and dependency graph, so it is only rebuilding what it needs to. Incremental compilation in Zig isn&#39;t functional yet, so this still recompiles a considerable amount of code. Additionally, as with the prior section, &lt;strong&gt;this is still using LLVM&lt;/strong&gt;. By simply dropping LLVM out of the picture, I expect this time to drop to around 12 seconds or so (less the time LLVM is emitting).&lt;/p&gt;
&lt;p&gt;Going further, once Zig supports incremental compilation, I expect we&#39;ll be able to measure incremental builds like this within milliseconds at worst. But, let&#39;s wait and see when that is reality.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Incremental Build (libghostty-vt)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Zig 0.14: 2sec 884ms&lt;/li&gt;
&lt;li&gt;Zig 0.15: 975ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the time it takes to rebuild only &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming&quot;&gt;libghostty-vt&lt;/a&gt; after a one-line change. Unlike the Ghostty executable, &lt;code&gt;libghostty-vt&lt;/code&gt; is fully functional with the self-hosted x86_64 backend, so this shows the differences in build times without LLVM in the picture.&lt;/p&gt;
&lt;p&gt;Similar to the Ghostty executable, this is still rebuilding the full Zig module for &lt;code&gt;libghostty-vt&lt;/code&gt;, since incremental compilation isn&#39;t fully functional yet. I expect this to also drop to single-digit milliseconds at worst once incremental compilation is a reality.&lt;/p&gt;
&lt;p&gt;But still, a sub-second build time for a non-trivial library is &lt;em&gt;amazing&lt;/em&gt;. This is the library I&#39;m spending most of my time working on right now, and even in a few short days since upgrading to Zig 0.15.1, I&#39;ve felt a huge difference in my workflow. Previously, I might tab out to read an email between builds or tests, but now its so fast I can stay in flow in my terminal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This improvement is most indicative of what&#39;s to come in the short term.&lt;/strong&gt; The self-hosted x86_64 backend is already stable enough to build all debug builds by default and the aarch64 backend is getting there, too. We aren&#39;t able to build the full Ghostty executable yet, but I bet this will get ironed out within months.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Faster Builds Are Here&lt;/h2&gt;
&lt;p&gt;As you can see, building Ghostty with Zig 0.15.1 is faster in every single scenario, despite the fact that a lot of Ghostty still can&#39;t even take advantage of the self-hosted backend! And despite the fact that incremental compilation isn&#39;t functional yet!&lt;/p&gt;
&lt;p&gt;I&#39;ve loved betting on Zig for Ghostty, and I love that they&#39;re focusing on compile times. These improvements are real, and they&#39;re here now. And I suspect in the next couple years, the results posted today will look downright slow. 😜&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Timestamped link: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://youtu.be/5eL_LcxwwHg?t=565&quot;&gt;https://youtu.be/5eL_LcxwwHg?t=565&lt;/a&gt; &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-builds-getting-faster#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This ignores an astronomical amount of work that has gone into making every aspect of the Zig compiler faster, more parallelizable, etc. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-builds-getting-faster#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All measurements done on the same x86_64 Linux machine. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-builds-getting-faster#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:6b383cd360c13a5367af782178a3efc18a921b27</id>
    <title type="text">Libghostty Is Coming</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-09-22-libghostty-is-coming/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/libghostty-is-coming"/>
    <published>2025-09-22T00:00:00+00:00</published>
    <updated>2025-09-22T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">Over two years ago, in one of my first public talks about Ghostty, I shared my vision for libghostty: an embeddable library for any application to embed their own fully functional, modern, and fast terminal emulator. Libghostty is finally…</summary>
    <content type="html">&lt;p&gt;Over two years ago, in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-and-useful-zig-patterns&quot;&gt;one of my first public talks about Ghostty&lt;/a&gt;, I shared my vision for &lt;code&gt;libghostty&lt;/code&gt;: an embeddable library for any application to embed their own fully functional, modern, and fast terminal emulator. Libghostty is finally starting to take shape, and I&#39;m excited to share more details about my plans for it.&lt;/p&gt;
&lt;p&gt;The first libghostty library will be &lt;code&gt;libghostty-vt&lt;/code&gt;: a zero-dependency library that provides an API for parsing terminal sequences and maintaining terminal state, extracted directly from Ghostty&#39;s real-world proven core. It doesn&#39;t even require libc!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This post is predominantly a roadmap update for libghostty and announcing what will be the first shippable component of it. The Zig API is available for testing now, but the C API is not ready and will be coming very shortly. In both cases, it is early testing quality and not ready for general usage.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why libghostty?&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with some background on why I believe libghostty must exist.&lt;/p&gt;
&lt;p&gt;There are hundreds of programs that implement some form of terminal emulation. The most obvious are the actual general purpose terminal emulators like Ghostty, Kitty, iTerm2, etc. But terminal multiplexers like &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/tmux/tmux&quot;&gt;tmux&lt;/a&gt; or &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://zellij.dev/&quot;&gt;zellij&lt;/a&gt; are also full terminal emulators&lt;picture class=&quot;article-picture article-picture-fallback&quot; style=&quot;--image-ratio:16 / 9&quot;&gt;&lt;img src=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fn-1&quot; alt=&quot;1&quot; loading=&quot;eager&quot; decoding=&quot;async&quot; fetchpriority=&quot;high&quot; referrerpolicy=&quot;no-referrer&quot; class=&quot;progressive-image&quot;&gt;&lt;/picture&gt; Editors embed their own terminal emulators too, such as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/JetBrains/jediterm&quot;&gt;jediterm&lt;/a&gt; for JetBrains products, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://xtermjs.org/&quot;&gt;Xterm.js&lt;/a&gt; for VS Code, or &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/zed-industries/zed/blob/a0514af58955a21401b4c10918a45c9c241a4a74/Cargo.toml#L444&quot;&gt;Alacritty&lt;/a&gt; in Zed.&lt;/p&gt;
&lt;p&gt;In addition to fully functional terminal emulators, many websites and applications implement read-only terminal emulation to display logs or command output. For example, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.blog/news-insights/product-news/a-better-logs-experience-with-github-actions/#opening-the-door-to-a-more-colorful-experience&quot;&gt;GitHub Actions output&lt;/a&gt; parses simple color sequences (but not much else). And hosting providers like Vercel or Render implement a simple form of terminal emulation allowing line clearing and redrawing within build logs as well as parsing colors.&lt;/p&gt;
&lt;p&gt;Many of these implementations are ad-hoc, one-off solutions. They aren&#39;t using any shared library or codebase.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fn-2&quot;&gt;2&lt;/a&gt; Terminal emulation is a classic problem that appears simple on the surface but is riddled with unexpected complexities and edge cases.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fn-3&quot;&gt;3&lt;/a&gt; As a result, most of these implementations are incomplete, buggy, and slow.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fn-4&quot;&gt;4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Beyond correctness, implementing any form of terminal emulation is a &lt;em&gt;waste of time&lt;/em&gt; for most developers. Terminal emulation is not the core business of JetBrains, Visual Studio Code, GitHub, Vercel, Render, etc. It&#39;d benefit them if they could have a stable, reusable solution that&#39;s consistent everywhere.&lt;/p&gt;
&lt;p&gt;My answer to this is libghostty: a cross-platform, minimal dependency library that exposes a C API so feature-rich, correct, and fast terminal functionality can be embedded by any application anywhere.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Beginning: &lt;code&gt;libghostty-vt&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The first libghostty library will be &lt;code&gt;libghostty-vt&lt;/code&gt;: a zero-dependency (not even libc) library that provides an API for parsing terminal sequences and maintaining terminal state such as cursor position, current styles, text wrapping, and more.&lt;/p&gt;
&lt;p&gt;Parsing terminal sequences is the most core functionality of a terminal emulator, and is required by full terminal emulators like Ghostty down to simple read-only style-only views such as GitHub Actions or Vercel build output.&lt;/p&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://vt100.net/emu/dec_ansi_parser&quot;&gt;state diagram&lt;/a&gt; might appear relatively simple at first glance, but an implementation is unexpectedly challenging to get right. For example, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/JetBrains/jediterm/pull/311&quot;&gt;Jediterm doesn&#39;t handle intermediates correctly&lt;/a&gt;, causing the widely supported &amp;quot;change cursor shape&amp;quot; sequence to swallow a character in every JetBrains editor at the time of this post.&lt;/p&gt;
&lt;p&gt;For style-only parsing, many developers skip the full state diagram. Instead, they do some light web searching, parse simple ANSI sequences such as &lt;code&gt;\e[31&lt;/code&gt; or &lt;code&gt;\e[41m&lt;/code&gt;, and claim &amp;quot;color support.&amp;quot; But style-only sequences are vastly more complex than that, for example they support RGB which itself can be in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/8cb52323e5575877eef44029c153eda006cece80/src/terminal/color.zig#L400-L438&quot;&gt;a dozen formats&lt;/a&gt;. And I still haven&#39;t found a single web console that renders &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/8cb52323e5575877eef44029c153eda006cece80/src/terminal/Parser.zig#L672-L722&quot;&gt;this complex style sequence&lt;/a&gt; correctly.&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fn-5&quot;&gt;5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;libghostty-vt&lt;/code&gt; aims to fix all of this.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;libghostty-vt&lt;/code&gt; is extracted from Ghostty and inherits all of the real world benefits: SIMD-optimized parsing, very good Unicode support, highly optimized memory usage, a robust fuzzed and Valgrind-tested codebase, excellent feature compatibility such as parsing Kitty Graphics Protocol or Tmux Control Mode, and more.&lt;/p&gt;
&lt;p&gt;All of this is packaged up into a single zero-dependency C API (it doesn&#39;t even rely on libc), allowing it to be easily embedded into any popular language ecosystem.&lt;/p&gt;
&lt;p&gt;Given the minimal footprint, &lt;code&gt;libghostty-vt&lt;/code&gt; will be &lt;strong&gt;widely portable.&lt;/strong&gt; Initial targets will be macOS and Linux for both &lt;code&gt;x86_64&lt;/code&gt; and &lt;code&gt;aarch64&lt;/code&gt; architectures, since those are the primary targets for Ghostty the application. But I plan to expand support to additional targets such as Windows, embedded devices, and the web via WASM. &lt;code&gt;libghostty&lt;/code&gt; will have broader support than Ghostty the GUI, due to its tighter scope.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Long Term&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;libghostty-vt&lt;/code&gt; is just the beginning. Longer term, we will provide more &lt;code&gt;libghostty-&amp;lt;x&amp;gt;&lt;/code&gt; libs that expose additional functionality such as input handling (keyboard encoding is a big one), GPU rendering (provide us with an OpenGL or Metal surface and we&#39;ll take care of the rest), GTK widgets and Swift frameworks that handle the entire terminal view, and more.&lt;/p&gt;
&lt;p&gt;As fundamental pieces stabilize, we will continue to offer more and more functionality. These will be structured as a family of libraries to minimize dependency requirements, code size, and overall maintenance complexity.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;libghostty-vt&lt;/code&gt; Status&lt;/h2&gt;
&lt;p&gt;I just merged the pull request &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/8840&quot;&gt;exposing &lt;code&gt;libghostty-vt&lt;/code&gt; as a Zig module&lt;/a&gt;. This PR includes a minimal example program, too. If you&#39;re a Zig developer, you can start experimenting with &lt;code&gt;libghostty-vt&lt;/code&gt; immediately.&lt;/p&gt;
&lt;p&gt;The C API isn&#39;t ready yet, but it is what I&#39;m working on &lt;em&gt;right now&lt;/em&gt; and it&#39;ll be available for testing soon. All of the work required is defining the C API, since the core logic is of course all there and has been used by Ghostty for years. Plus, the Ghostty macOS app already consumes an &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/include/ghostty.h&quot;&gt;internal-only C API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you look at the internal-only C header, please ignore the mess. It isn&#39;t a &lt;em&gt;good&lt;/em&gt; C API. It is internal-only and exists to satisfy the needs of the macOS application. This isn&#39;t a generally consumable libghostty, although it is used by &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://orbstack.dev/&quot;&gt;real commercial products already&lt;/a&gt; to embed Ghostty. We&#39;ll take a clean slate approach defining the C API for wider usage.&lt;/p&gt;
&lt;p&gt;I plan to version &lt;code&gt;libghostty&lt;/code&gt; separately from Ghostty the application. This blog post marks the public alpha (not promising API stability) and I&#39;m hoping to motivate some developers to come use it and eventually write some language bindings once the C API is ready.&lt;/p&gt;
&lt;p&gt;I hope to ship a tagged version of &lt;code&gt;libghostty-vt&lt;/code&gt; within the next 6 months, but it&#39;ll all depend on if its ready or not.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Looking for Feedback&lt;/h2&gt;
&lt;p&gt;We&#39;re at the critical stage of libghostty where we&#39;re designing the API, and the best way to design an API is with feedback from real consumers. Ghostty is one consumer, we have some community members working on other libghostty-consuming projects, but we could use as many as we can get!&lt;/p&gt;
&lt;p&gt;If you see a use case in your projects or organization for libghostty, please &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://discord.gg/ghostty&quot;&gt;join the Ghostty Discord&lt;/a&gt; and collaborate with the developers working on this. If you don&#39;t want to join Discord, email me (email in the footer of this website).&lt;/p&gt;
&lt;p&gt;The state of &lt;code&gt;libghostty&lt;/code&gt; at this stage could be considered &lt;em&gt;alpha&lt;/em&gt;, so don&#39;t expect a polished, stable experience. We&#39;re looking for hackers that want to get on the ground early.&lt;/p&gt;
&lt;p&gt;The &amp;quot;alpha&amp;quot; quality is with respect to the API (functions and types) itself. The core logic is shared with Ghostty and is extremely stable and proven in the real world.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Next Frontier&lt;/h2&gt;
&lt;p&gt;I&#39;m super excited that Ghostty the application has finally reached the stability where we can start moving towards the &lt;code&gt;libghostty&lt;/code&gt; goal. &lt;code&gt;libghostty&lt;/code&gt; is the next frontier for Ghostty and I think it has the ability to make a far larger impact than Ghostty can as a standalone application itself.&lt;/p&gt;
&lt;p&gt;Don&#39;t worry, there&#39;s plenty of changes for Ghostty the application and none of this diminishes my excitement or plans for that. Wider usage of &lt;code&gt;libghostty&lt;/code&gt; will result in a more feature rich and stable Ghostty application, too, since Ghostty itself is a consumer of &lt;code&gt;libghostty&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Boo. 👻&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;These own the pty to their children, parse all the escape codes, manage screen state, and ultimately &amp;quot;render&amp;quot; by emitting their own escape codes to a parent terminal emulator. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Many websites do use Xterm.js and the GTK ecosystem has &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://gitlab.gnome.org/GNOME/vte&quot;&gt;libvte&lt;/a&gt;. But this only covers a tiny fraction of the landscape and each of the aforementioned examples are limited in their own scope. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&#39;ve spent the better part of 3 years working on terminal emulation and we&#39;re &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/7471&quot;&gt;still finding weird edge cases&lt;/a&gt;. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Here are a couple real examples of impactful issues: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/JetBrains/jediterm/pull/311&quot;&gt;Jediterm didn&#39;t handle intermediates properly&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/dankamongmen/notcurses/blob/e8b4c7958ace55c4b09b5337a0ed6031f5eb8aba/src/lib/termdesc.c#L1052-L1061&quot;&gt;Apple&#39;s Terminal.app just bleeds DCS sequences into the output&lt;/a&gt;. I&#39;m not calling anyone out specifically, just showing that these issues exist and terminal emulation is very hard to get right. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A lot of general terminal emulators also got this wrong, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/5033&quot;&gt;including Ghostty just 9 months ago&lt;/a&gt;. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/libghostty-is-coming#user-content-fnref-5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:00a3cef82056106a173acb59cc8d02e89443a656</id>
    <title type="text">You Have to Feel It</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-08-30-you-have-to-feel-it/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/feel-it"/>
    <published>2025-08-30T00:00:00+00:00</published>
    <updated>2025-08-30T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">You see a series of checkboxes checked. Schedules met. Requirements satisfied. Demos delivered. It&#39;s a good day. Good job, you, good job! A promotion is in sight. But you didn&#39;t feel it. You didn&#39;t feel it. We, as people, feel something…</summary>
    <content type="html">&lt;p&gt;You see a series of checkboxes checked. Schedules met. Requirements satisfied. Demos delivered. It&#39;s a good day. Good job, you, good job! A promotion is in sight.&lt;/p&gt;
&lt;p&gt;But you didn&#39;t feel it. &lt;em&gt;You didn&#39;t feel it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We, as people, feel something with every interaction. Frustration, joy, relief, confidence. A feeling. A person interacts with our work. Our work evokes a feeling. The feeling matters. The feeling is part of the work. The &lt;em&gt;desired feeling&lt;/em&gt; is part of the requirements.&lt;/p&gt;
&lt;p&gt;When you feel it, you know. The feature makes you smile when you use it. It fits right in, like it was always meant to be there. You want to use it again. You want to tell people about it.&lt;/p&gt;
&lt;p&gt;This is the difference. This is what metrics, specifications, and demos miss. They don&#39;t capture the feeling. For the people who will use and live in the work, the feeling is part of their daily experience. Which means you can&#39;t stop at checking the boxes on paper. You have to sit with it, use it, live with it.&lt;/p&gt;
&lt;p&gt;You have to feel it.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:f14ef888a26df8ae52420b69c4b3f172a89d134f</id>
    <title type="text">Advice for Tech Non-Profits</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-08-20-advice-for-tech-non-profits/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/advice-for-tech-nonprofits"/>
    <published>2025-08-20T00:00:00+00:00</published>
    <updated>2025-08-20T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">My family and I regularly contribute to various philanthropic causes. In the range of contributions we make, I&#39;ve noticed that technical non-profits are far and away the worst at attracting eager donors like myself when compared to other…</summary>
    <content type="html">&lt;p&gt;My family and I regularly contribute to various philanthropic causes. In the range of contributions we make, I&#39;ve noticed that technical non-profits are far and away the worst at attracting eager donors like myself when compared to other causes.&lt;/p&gt;
&lt;p&gt;In this blog post, I&#39;ll share what I&#39;ve learned from my own activity with other causes as well as what I&#39;ve learned from spending more time with donors in general. This blog post will be biased towards larger donations, but I think many of these concepts translate nicely to smaller donations as well.&lt;/p&gt;
&lt;p&gt;I am highlighting tech non-profits specifically because these are all concepts that I find other causes get right more often than not, whereas tech consistently gets them wrong. I also have a bias towards wanting technical organizations to become more successful.&lt;/p&gt;
&lt;h2&gt;How do I donate?&lt;/h2&gt;
&lt;p&gt;It is surprisingly difficult to simply figure out: how am I supposed to give you money?&lt;/p&gt;
&lt;p&gt;Tech projects usually nail the metaphorical swipe-a-credit-card donations to provide small one-time amounts and specific smaller tiers ($X/month). But a lot of donors don&#39;t work this way. I don&#39;t work this way.&lt;/p&gt;
&lt;p&gt;I donate through a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.irs.gov/charities-non-profits/charitable-organizations/donor-advised-funds&quot;&gt;donor-advised fund (DAF)&lt;/a&gt;. To donate, I need -- at a minimum -- the verifiable EIN of a tax-exempt organization (DAFs can only contribute to registered non-profits). On top of that, I usually need a physical address and the contact information for a real human that can work with the fund managers to process the donation. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.jackpearkes.com/posts/donor-advised-funds&quot;&gt;DAFs are just one of many vehicles&lt;/a&gt; for donation, though.&lt;/p&gt;
&lt;p&gt;The best non-profits have a dedicated &amp;quot;Donate&amp;quot; page that has an obvious and easily accessible list of donation methods: credit card, check, donor-advised funds, employer matching, stocks, legacy giving (will, trust, retirement accounts), etc. Each donation method leads to its own dedicated page with &lt;em&gt;very specific information&lt;/em&gt; about how to donate via that method.&lt;/p&gt;
&lt;p&gt;It may sound obvious, but you &lt;strong&gt;want to make it as easy as possible to initiate the financial transaction&lt;/strong&gt;. And yet, a lot of non-profits make this needlessly difficult.&lt;/p&gt;
&lt;h2&gt;Why should I donate?&lt;/h2&gt;
&lt;p&gt;Attracting donors is not so different from attracting customers: you have to engage in some level of marketing. For non-profits, the question is: why should I donate?&lt;/p&gt;
&lt;p&gt;Technical people tend to be bad at this for their own technical projects. So its not surprising to me that technical people are also often bad at this for their non-profits they&#39;re involved with.&lt;/p&gt;
&lt;p&gt;I, too, used to have the attitude of &amp;quot;the value is obvious&amp;quot; or perhaps the idea that &amp;quot;if you build it, they will come.&amp;quot; This is true for a small, small segment of the &amp;quot;market,&amp;quot;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fn-1&quot;&gt;1&lt;/a&gt; but for the much larger segment, you have to educate.&lt;/p&gt;
&lt;p&gt;Aim to answer the following questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What do you do?&lt;/li&gt;
&lt;li&gt;Why is what you do important?&lt;/li&gt;
&lt;li&gt;Who is benefiting from what you do?&lt;/li&gt;
&lt;li&gt;Why do you need money to do what you do?&lt;/li&gt;
&lt;li&gt;What have you done recently?&lt;/li&gt;
&lt;li&gt;How has money enabled you to do what you did recently?&lt;/li&gt;
&lt;li&gt;How much money do you need to do what you do?&lt;/li&gt;
&lt;li&gt;Why do you need that much? Is it going up, or down? Why?&lt;/li&gt;
&lt;li&gt;How are you spending your money?&lt;/li&gt;
&lt;li&gt;Who else is donating?&lt;/li&gt;
&lt;li&gt;Why aren&#39;t more people donating?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A lot of these questions -- especially the financial ones -- can be answered by reading an &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.irs.gov/forms-pubs/about-form-990&quot;&gt;IRS Form 990&lt;/a&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fn-2&quot;&gt;2&lt;/a&gt;, but giving a more human-friendly version of the information is always helpful.&lt;/p&gt;
&lt;p&gt;Technical projects often don&#39;t answer any of this. They either consider their value obvious or simply point to the projects they build and expecting the viewer to gather enough information based on that.&lt;/p&gt;
&lt;p&gt;But that isn&#39;t enough. I&#39;m a technical person, I can look at a project and understand its technical value. I can conclude &amp;quot;yes, this is a cool thing that I&#39;d like to continue to exist.&amp;quot; But that doesn&#39;t answer the very basic questions of: how much and why? Beyond that, there&#39;s so many more questions I have (as listed above). If you want to attract more donations and appeal to a broader donor base, you must answer these very basic questions.&lt;/p&gt;
&lt;h2&gt;What is my donation doing?&lt;/h2&gt;
&lt;p&gt;A non-profit typically issues an annual impact report. I&#39;ve only seen a couple technical non-profits produce an impact report and of those, they were extremely amateur compared to the average non-technical non-profit. Most don&#39;t produce an impact report at all, which is a terrible mistake.&lt;/p&gt;
&lt;p&gt;An impact report is the human-friendly annual report of what the organization achieved and its goals for subsequent years. It usually includes a report of the financial health of the organization, as well.&lt;/p&gt;
&lt;p&gt;The couple technical ones I&#39;ve seen &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fn-3&quot;&gt;3&lt;/a&gt; did a good job on the what, but it read too much like a product keynote. The what was good! But its &lt;em&gt;more&lt;/em&gt; important to relate that to finances: how did the finances lead to that? how will more finances achieve more? what is the strategy for increasing fundraising?&lt;/p&gt;
&lt;p&gt;These are all important questions for few reasons.&lt;/p&gt;
&lt;p&gt;First, a donor generally doesn&#39;t want to be part of a small group of donors. I specifically care deeply that the organization will have a healthy group of donors so that I&#39;m just a piece. Fundraising goals and marketing is part of that. I&#39;ll donate more if I know more people are donating.&lt;/p&gt;
&lt;p&gt;Second, the larger the fundraised amount (either individually or in aggregate), the more I want to see &lt;em&gt;results&lt;/em&gt;, mainly so that the money isn&#39;t being wasted. I think most non-profits aren&#39;t &lt;em&gt;trying&lt;/em&gt; to waste money, but the goal of a non-profit is to achieve some public good, and its easy to inefficiently allocate funds. Non-profits especially can&#39;t afford to be inefficient allocating funds (but too many are).&lt;/p&gt;
&lt;p&gt;To that end, impact reports should also contain &lt;em&gt;measurable goals&lt;/em&gt;. And in subsequent reports they should contain &lt;em&gt;reflections&lt;/em&gt; on those goals. One thing I really try to look for is cases where a goal was missed and how that organization reacted. As with other things, how one deals with failure often speaks more loudly than how one deals with success.&lt;/p&gt;
&lt;p&gt;Some people misunderstand the desire for &lt;em&gt;measurable goals&lt;/em&gt; and results as a &lt;em&gt;growth mindset&lt;/em&gt; and needing to do more, more, more. That&#39;s not the ask. The goals are about accountability, even if they&#39;re steady goals.&lt;/p&gt;
&lt;p&gt;If I can&#39;t find that information, its okay, because the next thing I&#39;ll do is try to talk to a real human, which I cover next.&lt;/p&gt;
&lt;h2&gt;Unreasonable Effectiveness of Human Connection&lt;/h2&gt;
&lt;p&gt;Talking to a human is unreasonably effective. You can discover information, align goals, resolve arguments, and more so much more quickly and easily by talking to someone face to face.&lt;/p&gt;
&lt;p&gt;Technical non-profits tend to lack a meaningful amount of face to face connection. They lean too hard on asynchronous, textual mediums such as IRC, Matrix, or email. This sort of conversation may work for technical work and communities, but it doesn&#39;t work for financial transactions (non-profit or otherwise).&lt;/p&gt;
&lt;p&gt;I can understand the expectation that donors should do their own research, consume the various written material, and be self-sufficient. This is how a lot of technical projects -- especially open source -- work. But that&#39;s not how you do sales, and a non-profit is ironically a strongly sales-driven organization.&lt;/p&gt;
&lt;p&gt;Let me repeat that, because its the uncomfortable truth that a lot of people don&#39;t like to process: &lt;strong&gt;non-profit organizations are sales-driven organizations.&lt;/strong&gt; You have to engage in a lot of the standard tactics a business has to in order to &lt;em&gt;attract and retain donors&lt;/em&gt; in order to pursue your mission statement (which, for a non-profit, is by definition a public good, which is very nice).&lt;/p&gt;
&lt;p&gt;My advice for technical organizations is to make it easy to get in touch with a real human being via phone (yes, a phone!) or at worst a video call. In 30 minutes to an hour, a prospective or current donor can not only receive information, but connect in a way that is very difficult to do in other ways.&lt;/p&gt;
&lt;p&gt;This is also a retention strategy. Schedule meetings with your existing larger donors. If you&#39;re in town (or they&#39;re particularly heavy donors), try to schedule dinners. If you&#39;re going to be at the same conference, schedule a lunch or drinks. Use this time to remind them of the mission, the recent achievements, how much they&#39;re helping, etc.&lt;/p&gt;
&lt;p&gt;Honestly, become friends. I genuinely mean this. For for-profit organizations, it can be a lot more transactional. For non-profits that are mission-driven, these are usually people that are highly philosophically aligned and its likely you&#39;ll get along at a personal level. Become friends, it makes the whole thing a lot more fun for both parties.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A for-profit is a machine that takes money and returns more money to investors. A non-profit is a machine that takes money and returns social impact to donors. They&#39;re very different, and yet uncomfortably alike.&lt;/p&gt;
&lt;p&gt;I believe people sometimes tend to believe a non-profit organically gets donations from little-known sources -- the larger group of do-gooders. But, most successful non-profits I&#39;ve worked with (even the smaller ones) that also do the best work have people &lt;em&gt;aggressively&lt;/em&gt; working on the &amp;quot;business&amp;quot; of donations. This should be unsurprising, since the ability of most non-profits in the early stage is directly correlated to the funding they can get &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fn-4&quot;&gt;4&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used to wonder why a non-profit needs a CEO/President, sometimes with a group of full time employees. There&#39;s a lot of reasons, but attracting and retaining donors is a big, big job. Actually, its also a very well defined job, typically called &amp;quot;non-profit development.&amp;quot;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fn-5&quot;&gt;5&lt;/a&gt; You can even go to school for it, similar to studying business management.&lt;/p&gt;
&lt;p&gt;I want to see new and existing technical non-profits be successful. I think the vast ecosystem of existing technical non-profits are leaving a ton on the table by being -- quite frankly -- terrible at attracting and retaining donors. I hope this blog post helps illuminate the work involved and paints a roadmap of how non-profits can improve.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I&#39;m sorry, but despite being a non-profit, you inherit a lot of the same challenges that a business has. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&#39;ve gotten really adept at reading 990s. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I don&#39;t want to link them because I&#39;m not trying to call anyone out. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At later stages with larger dollar amounts, it can be hard to scale that success. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you web search for &amp;quot;non-profit development&amp;quot; you&#39;ll find many resources. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/advice-for-tech-nonprofits#user-content-fnref-5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:994d40ca25042a758861f723517a0af1f485adac</id>
    <title type="text">We Rewrote the Ghostty GTK Application</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-08-14-we-rewrote-the-ghostty-gtk-application/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/ghostty-gtk-rewrite"/>
    <published>2025-08-14T00:00:00+00:00</published>
    <updated>2025-08-14T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">We just completed rewriting the Ghostty GTK application fully embracing the GObject type system from Zig and also verifying with Valgrind every step of the way. The result is a more feature rich, stable, and maintainable Ghostty on Linux…</summary>
    <content type="html">&lt;p&gt;We &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/8235&quot;&gt;just completed&lt;/a&gt; rewriting the Ghostty GTK application fully embracing the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://docs.gtk.org/gobject/concepts.html&quot;&gt;GObject type system&lt;/a&gt; from Zig and also verifying with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://valgrind.org/&quot;&gt;Valgrind&lt;/a&gt; every step of the way. The result is a more feature rich, stable, and maintainable Ghostty on Linux and BSD.&lt;/p&gt;
&lt;p&gt;There are multiple interesting, technical topics from this process, but I want to focus in on two (1) interfacing with the GObject type system from Zig and (2) verifying a GTK application with Valgrind and reflecting on the memory issues Valgrind found in a Zig codebase.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;First, some quick background. Ghostty is a cross-platform (macOS, Linux, FreeBSD) terminal emulator. Ghostty sets itself apart from other cross-platform terminal emulators by using a platform-native application or GUI framework for each platform &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-gtk-rewrite#user-content-fn-1&quot;&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On macOS, Ghostty is a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/tree/main/macos&quot;&gt;multi-thousand line Swift application&lt;/a&gt; built with Xcode. On Linux and BSD, Ghostty is a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/tree/b7913f09ad5326331192a24af53473ec541bf1af/src/apprt/gtk-ng&quot;&gt;multi-thousand line GTK application&lt;/a&gt; leveraging direct integrations with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/src/apprt/gtk-ng/winproto/x11.zig&quot;&gt;X11&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/src/apprt/gtk-ng/winproto/wayland.zig&quot;&gt;Wayland&lt;/a&gt;, etc. Tying it all together, there is a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/tree/main/src&quot;&gt;very large shared core written in Zig&lt;/a&gt; that exports a C ABI compatible API.&lt;/p&gt;
&lt;p&gt;For full motivation on why Ghostty was the way it was before and why we decided to rewrite the GTK application now, see the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/pull/7961&quot;&gt;original &amp;quot;gtk-ng&amp;quot; PR&lt;/a&gt;. I&#39;m going to keep this post focused on the takeaways rather than the motivation.&lt;/p&gt;
&lt;h2&gt;GObject Type System and Zig&lt;/h2&gt;
&lt;p&gt;Whatever your feelings are about OOP and memory management, the reality is that if you choose GTK, you&#39;re forced into interfacing in &lt;em&gt;some way&lt;/em&gt; with the GObject type system. You can&#39;t avoid it.&lt;/p&gt;
&lt;p&gt;Well you &lt;em&gt;can avoid it&lt;/em&gt; and we &lt;em&gt;did avoid it&lt;/em&gt;. And it leads to a mess trying to tie the lifetimes of your non-reference-counted objects to the reference counted ones. There was an entire class of bug that kept popping up in the Ghostty GTK application that could basically be summed up as: the Zig memory or the GTK memory has been freed, but not both.&lt;/p&gt;
&lt;p&gt;Besides the correctness issues, avoiding the object system also forced us away from using GTK-native features such as signals (events), properties (which can be bound to by GUI elements), actions (invoking one-way behavior from afar), and more.&lt;/p&gt;
&lt;p&gt;Let&#39;s look at a concrete example: reloadable configuration. The configuration in Ghostty is represented by a Zig-owned &lt;code&gt;Config&lt;/code&gt; structure. Many different parts of the GUI need to be aware of the configuration: windows, tabs, menus, splits, etc.&lt;/p&gt;
&lt;p&gt;Reloading configuration was a complicated, CPU-intensive (relatively), and error-prone task because we had to ensure the &lt;em&gt;entire GUI&lt;/em&gt; updated before we could free the old &lt;code&gt;Config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, the Zig &lt;code&gt;Config&lt;/code&gt; structure is wrapped in a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/b7913f09ad5326331192a24af53473ec541bf1af/src/apprt/gtk-ng/class/config.zig&quot;&gt;reference-counted &lt;code&gt;GhosttyConfig&lt;/code&gt; GObject&lt;/a&gt;. When we reload the config, we overwrite our property and let the GObject property change notification system ripple through the application (sometimes across multiple event loop ticks). When the old configuration no longer has any references, it frees. Conceptually much simpler.&lt;/p&gt;
&lt;p&gt;In addition to memory management, we can now more easily create custom GTK widgets. This let us fully embrace modern GTK UI technologies such as &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://gitlab.gnome.org/GNOME/blueprint-compiler&quot;&gt;Blueprint&lt;/a&gt;. For example, here is our &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/b7913f09ad5326331192a24af53473ec541bf1af/src/apprt/gtk-ng/ui/1.5/window.blp&quot;&gt;terminal window Blueprint file&lt;/a&gt;. This has already led to more easily introducing GUI features like a new GTK titlebar tabs option, an animated border on bell, etc.&lt;/p&gt;
&lt;h2&gt;Valgrind with GTK and Zig&lt;/h2&gt;
&lt;p&gt;This topic deserves an entire blog post on its own. The gist of it is that from the first PR to the last, we&#39;ve run every change and Ghostty feature through Valgrind and addressed any issues to ensure that there are no memory leaks, undefined memory access, etc.&lt;/p&gt;
&lt;p&gt;Running Valgrind on a GTK application is pretty nasty. We need a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/valgrind.supp&quot;&gt;pretty large suppression file&lt;/a&gt;. I know its a lot, but 80% of that file is provided by GTK itself. The remainder is primarily 3rd party libraries and GPU drivers. There are perhaps one or two suppressions I find suspicious (and commented as such).&lt;/p&gt;
&lt;p&gt;The important part is we were able to identify a number of bugs along the way that would&#39;ve &lt;em&gt;definitely&lt;/em&gt; slipped under the radar. For example, I learned that if you &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/commit/7548dcfe634cd9447e0b7a0f5e2900fe7094a225&quot;&gt;forget to clear a GObject &lt;code&gt;WeakRef&lt;/code&gt; during dispose&lt;/a&gt;, it&#39;ll cause undefined memory access in the &lt;em&gt;target (referenced)&lt;/em&gt; object when it disposes at some point in the future (can be hours, days!). That undefined memory access happens to be &lt;em&gt;fine&lt;/em&gt; 99% of the time, but once in awhile it causes a crash. Fun! Valgrind found this without problem.&lt;/p&gt;
&lt;p&gt;Memory safety seems to... erm... &lt;em&gt;activate&lt;/em&gt; certain conversations. So let me say two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Our Zig codebase had one leak and one undefined memory access.&lt;/strong&gt; That was &lt;em&gt;really surprising to me&lt;/em&gt; (in a good way). Our Zig codebase is large, complex, and uses tons of memory tricks for performance that could easily lead to unsafe behaviors. I thought we&#39;d have a lot more issues, honestly. Also, the one leak found was during calling a 3rd party C API (so Zig couldn&#39;t detect it). So this is a huge success.&lt;/p&gt;
&lt;p&gt;Zig has a leak-detecting debug allocator and various safety checks that are only on during debug and test builds in the Ghostty project. Additionally, Zig has integrations with Valgrind. For example, Zig emits a Valgrind client request to mark some memory as undefined whenever you set a value as &lt;code&gt;undefined&lt;/code&gt; (keyword) in Zig. This helps find even more issues.&lt;/p&gt;
&lt;p&gt;This experience has really shown me that this is &lt;em&gt;working&lt;/em&gt;, despite not having any of these protections in our release builds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;All other memory issues revolved around C API boundaries.&lt;/strong&gt; Every other issue we found (and there were dozens) was directly within the complex lifetimes of the GObject system or on C API boundaries. My takeaway here is that you absolutely need tooling like Valgrind to safely call across C APIs (even if they&#39;re not written in C).&lt;/p&gt;
&lt;p&gt;For most complex libraries that expose a C API, the C API represents a boundary where object lifetime is transferred or blurred. Whatever language you&#39;re using to interact with it, the safety you&#39;re guaranteed is only as good as understanding the semantics of the API and writing a good wrapper.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The features Zig provides around memory safety are well documented. There are a lot of academic or theoretical discussions about what Zig does or does not do and whether that&#39;s good or bad. Those are valuable discussions to be had, but so are empirical results. This process is showing empirical results from a large, complex, multi-threaded, multi-platform Zig project when every individual feature is run with scrutiny under Valgrind. Takeaway from that what you want, I don&#39;t want to start any flame wars!&lt;/p&gt;
&lt;p&gt;Going forward, I plan to continue to run every GTK PR within Valgrind and improving our project documentation so maintainers and contributors can do so as well (we already have a couple up and running!).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This is now my 5th time writing the GUI part of Ghostty from scratch: once with GLFW, once on macOS with SwiftUI, then on macOS with AppKit plus SwiftUI, once on Linux with GTK procedurally, and now on Linux with GTK and the full GObject type system.&lt;/p&gt;
&lt;p&gt;Each time, I&#39;ve learned something new and valuable, and I&#39;ve carried that experience into each iteration (and across platforms). Even this time, I&#39;ve learned some new tricks that I plan on taking back over to macOS.&lt;/p&gt;
&lt;p&gt;I want to also highlight that the entire GTK subsystem maintenance team hopped on board to help complete the rewrite. They did a lot of work, too.&lt;/p&gt;
&lt;p&gt;The new, rewritten Ghostty GTK application is now the default when you build Ghostty from source on &lt;code&gt;main&lt;/code&gt;, and will be shipped to everyone in the 1.2 release coming in just a few weeks.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Linux people get really worked up when I say &amp;quot;platform-native&amp;quot;. There is no such thing on Linux, but reasonable people agree that something like a GTK app (or Qt) feels &amp;quot;native&amp;quot; on &lt;em&gt;most desktops&lt;/em&gt; over other applications. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-gtk-rewrite#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:406f65adbdc8010512ff2676b4c2b826f88fedc8</id>
    <title type="text">&quot;As Code&quot;</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-03-04-as-code/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/as-code"/>
    <published>2025-03-04T00:00:00+00:00</published>
    <updated>2025-03-04T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">I&#39;ve always personally defined the &quot;as code&quot; suffix in &quot;X as Code&quot; to mean &quot;a system of principles or rules&quot;1. This is in contrast to the popular interpretation of &quot;as code&quot; to mean &quot;as programming.&quot; I feel the need to write this short…</summary>
    <content type="html">&lt;p&gt;I&#39;ve always personally defined the &amp;quot;as code&amp;quot; suffix in &amp;quot;X as Code&amp;quot; to mean &amp;quot;a system of principles or rules&amp;quot;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fn-1&quot;&gt;1&lt;/a&gt;. This is in contrast to the popular interpretation of &amp;quot;as code&amp;quot; to mean &amp;quot;as programming.&amp;quot;&lt;/p&gt;
&lt;p&gt;I feel the need to write this short blog post and clarify my intent because I&#39;m often personally a target of this misunderstanding due to my history of starting &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.terraform.io&quot;&gt;Terraform&lt;/a&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fn-2&quot;&gt;2&lt;/a&gt; which is a flagship &amp;quot;X as Code&amp;quot; tool.&lt;/p&gt;
&lt;p&gt;My intent with &amp;quot;X as Code&amp;quot;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fn-3&quot;&gt;3&lt;/a&gt; was always to get knowledge out of people&#39;s heads and into a more inscribed system. Once inscribed, knowledge and process can be shared, versioned, iterated upon, etc.&lt;/p&gt;
&lt;p&gt;The relation to programming is that codified knowledge tends to also be computer-readable and comes with the full benefits of that (e.g. automation, validation, etc). However, I don&#39;t believe that implies that all the requirements of modern programming should be applied to &amp;quot;as code&amp;quot; systems.&lt;/p&gt;
&lt;p&gt;As an additional point of history, I coauthored the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.hashicorp.com/en/tao-of-hashicorp&quot;&gt;Tao of HashiCorp&lt;/a&gt; over a decade ago (before Terraform!) and this mindset is still present in the two &amp;quot;codification&amp;quot; sections, which remain mostly unchanged since their original writing. This helps provide additional framing for my intent with &amp;quot;as code.&amp;quot;&lt;/p&gt;
&lt;p&gt;Please do not take this post as a criticism of the &amp;quot;as programming&amp;quot; ideals, a claim that &amp;quot;as code&amp;quot; is perfect, or that &amp;quot;as code&amp;quot; should always be interpreted this way &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fn-4&quot;&gt;4&lt;/a&gt;. None of these are true. I only want to clarify my personal intent and use of the term historically.&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.merriam-webster.com/dictionary/code&quot;&gt;https://www.merriam-webster.com/dictionary/code&lt;/a&gt; &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I haven&#39;t worked on Terraform in years and I left HashiCorp in 2023. I also only claim credit for &lt;em&gt;starting&lt;/em&gt; Terraform (and I wasn&#39;t alone); it has been maintained by others for many, many years now. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I did not coin the term &amp;quot;X as Code&amp;quot; or &amp;quot;Infrastructure as Code.&amp;quot; I don&#39;t know who did but it predates Terraform and my career. Some unsolicited business advice: don&#39;t create new market categories, enter existing ones and let your competitors bear the cost of educating the market. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The definitions of things is a social process, after all. Where the herd goes, so will we all. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/as-code#user-content-fnref-4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:673d585527ce711e41ee1b6faeefb80df30821e4</id>
    <title type="text">Welcoming Ghostty Subsystem Maintainers</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2025-02-07-welcoming-ghostty-subsystem-maintainers/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/ghostty-subsystem-maintainers"/>
    <published>2025-02-07T00:00:00+00:00</published>
    <updated>2025-02-07T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">I&#39;m excited to share that there are eight newly promoted subsystem maintainers for Ghostty. These are the first maintainers to be promoted since the project&#39;s creation, and I&#39;m thrilled to see the project grow in this way. Adding…</summary>
    <content type="html">&lt;p&gt;I&#39;m excited to share that there are &lt;strong&gt;eight&lt;/strong&gt; newly promoted subsystem maintainers for &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org&quot;&gt;Ghostty&lt;/a&gt;. These are the first maintainers to be promoted since the project&#39;s creation, and I&#39;m thrilled to see the project grow in this way. Adding additional, independent maintainers is an important part of any open source project&#39;s long-term health and stability.&lt;/p&gt;
&lt;p&gt;While Ghostty has only been public for a little over a month, the project was in private beta for almost two years. A benefit of this is that members of the community have already made significant contributions to the project and repeatedly shown themselves to be kind and helpful to others.&lt;/p&gt;
&lt;p&gt;All of the new maintainers announced today were part of the private beta and have been active contributors and community members for months if not over a year. They each have dozens of individual merged PRs into Ghostty already, but have also been helpful and active in issues, Discord, etc.&lt;/p&gt;
&lt;p&gt;Please join me in welcoming the following maintainers, in alphabetical order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/gpanders&quot;&gt;&lt;code&gt;@gpanders&lt;/code&gt;&lt;/a&gt; - Terminal Emulation&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/jcollie&quot;&gt;&lt;code&gt;@jcollie&lt;/code&gt;&lt;/a&gt; - Linux and GTK&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/jparise&quot;&gt;&lt;code&gt;@jparise&lt;/code&gt;&lt;/a&gt; - Shell Integration&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/pluiedev&quot;&gt;&lt;code&gt;@pluiedev&lt;/code&gt;&lt;/a&gt; - Linux and GTK&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/qwerasd205&quot;&gt;&lt;code&gt;@Qwerasd&lt;/code&gt;&lt;/a&gt; - Font and Rendering&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/rockorager&quot;&gt;&lt;code&gt;@rockorager&lt;/code&gt;&lt;/a&gt; - Terminal Emulation&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/trag1c&quot;&gt;&lt;code&gt;@trag1c&lt;/code&gt;&lt;/a&gt; - Discord bot&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/tristan957&quot;&gt;&lt;code&gt;@tristan957&lt;/code&gt;&lt;/a&gt; - Linux and GTK&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Subsystems&lt;/h2&gt;
&lt;p&gt;We&#39;ve decided to follow a subsystem maintainer model for Ghostty. Rather than promoting &amp;quot;core maintainers&amp;quot; who are responsible for every part of the project, we&#39;ve identified key subsystems within the project with subject matter experts and promoted these people to a position of authority within their respective subsystems.&lt;/p&gt;
&lt;p&gt;The exact &amp;quot;subsystems&amp;quot; emerge organically as contributors show themselves to be knowledgeable, excited, and interested in a particular area of the project. To start, we&#39;ve identified the following subsystems, in no particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Terminal Emulation&lt;/strong&gt; - Responsible for escape sequences, pty handling, subprocess management, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GTK&lt;/strong&gt; - Responsible for the GTK app runtime and Linux in general. Also includes the lower level X11/Wayland integrations, cgroup management, and so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt; - Responsible for the macOS app runtime and macOS-specific features, configurations, etc. No one was promoted to this subsystem today, but we expect to add maintainers here in the future.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Font&lt;/strong&gt; - Responsible for font discovery, rasterization, shaping, coloring, etc. Anything font-related. Covers all platforms, so these people have expertise with FreeType, CoreText, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Renderer&lt;/strong&gt; - Responsible for core rendering abstractions as well as specific renderers like OpenGL and Metal. More generally responsible for any issues related to rendering.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shell&lt;/strong&gt; - Responsible for shell completions, shell detection, and any other shell interactions. Expected to cover all supported shells, including bash, zsh, fish, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discord Bot&lt;/strong&gt; - Responsible for the Discord bot, which is an important part of our community management and support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Privileges, Responsibilities, and Goals&lt;/h2&gt;
&lt;p&gt;The goal of promoting subsystem maintainers is to enable Ghostty to scale more effectively. Issues can be resolved more quickly and improvements can be shipped faster when trusted maintainers have autonomy to make decisions within their subsystems.&lt;/p&gt;
&lt;p&gt;Subsystem maintainers have the ability to independently define the roadmap of their respective subsystems, triage issues, merge pull requests, etc.&lt;/p&gt;
&lt;p&gt;Subsystem maintainership is a voluntary role and maintainers are not expected to dedicate any amount of time to the project. However, if a maintainer is inactive for a long period of time, they may be removed from the maintainers list to avoid bitrot or outdated information.&lt;/p&gt;
&lt;p&gt;Notably, maintainers have NO OBLIGATION to review pull requests or issues in their subsystem. They have full discretion to review or not review anything they want. This isn&#39;t a job! It is a role of trust and authority and the expectation is that maintainers will use their best judgement and work on what they find interesting and rewarding for themselves and the project.&lt;/p&gt;
&lt;p&gt;For more specific details on the subsystem maintainer process, see the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/ghostty-org/ghostty/blob/main/CODEOWNERS&quot;&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/a&gt; file. There is a detailed comment that outlines the entire system.&lt;/p&gt;
&lt;h2&gt;Welcome and Thanks&lt;/h2&gt;
&lt;p&gt;I want to extend a personal thank you to each of the new maintainers both for accepting the role as well as their already significant contributions to the project. Besides that, they&#39;ve all just been wonderful people to get to know and work with.&lt;/p&gt;
&lt;p&gt;Please join me in welcoming these new maintainers to the Ghostty project. 👻&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en-US">
    <id>urn:aggr:item:c30407c9b38fa14f6f01957b72d9967629f216bf</id>
    <title type="text">Ghostty: Reflecting on Reaching 1.0</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/mitchellh-com/2024-12-26-ghostty-reflecting-on-reaching-1-0/"/>
    <link rel="via" type="text/html" href="https://mitchellh.com/writing/ghostty-1-0-reflection"/>
    <published>2024-12-26T00:00:00+00:00</published>
    <updated>2024-12-26T00:00:00+00:00</updated>
    <author>
      <name>Mitchell Hashimoto</name>
    </author>
    <category term="programming"/>
    <source>
      <title>Mitchell Hashimoto</title>
    </source>
    <summary type="text">This isn&#39;t a release announcement. Ghostty 1.0 is out. You can download it and read about it on the Ghostty website. This is a personal reflection on the project. Ghostty 1.0 is out. If you told me two years ago that I would be releasing a…</summary>
    <content type="html">&lt;p&gt;This isn&#39;t a release announcement. Ghostty 1.0 is out. You can download it and read about it &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org&quot;&gt;on the Ghostty website&lt;/a&gt;. This is a personal reflection on the project.&lt;/p&gt;
&lt;p&gt;Ghostty 1.0 is out.&lt;/p&gt;
&lt;p&gt;If you told me two years ago that I would be releasing a terminal emulator, I wouldn&#39;t have believed you. I&#39;ve always been a fan of the terminal, my entire career was built around shipping terminal-first software. But they&#39;re a solved problem, right? That&#39;s what I thought.&lt;/p&gt;
&lt;p&gt;I started the project in 2022 merely as a way to play with Zig, do some graphics programming, and deepen my understanding of terminals. I never intended to release it. I didn&#39;t think there was innovation to be had. I thought I would learn a lot over a few months and move on.&lt;/p&gt;
&lt;p&gt;But as I worked on it, I looked at other terminals differently. I saw tradeoffs that I didn&#39;t like. I saw features that I wanted. I saw performance that I could improve. I saw stagnation. There are many &lt;em&gt;fantastic&lt;/em&gt; terminals out there and you should use them if they work for you. But I wanted something different and thought maybe others did too.&lt;/p&gt;
&lt;p&gt;And so Ghostty was born. It&#39;s not perfect, it&#39;s not done, it&#39;s not for everyone. But it&#39;s mine. It&#39;s a reflection of my values and my vision for what a terminal could be.&lt;/p&gt;
&lt;p&gt;To learn more about how Ghostty differentiates itself from other terminals and my vision for the project, read the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-is-coming&quot;&gt;Ghostty 1.0 is coming&lt;/a&gt; post.&lt;/p&gt;
&lt;h2&gt;The Private Beta&lt;/h2&gt;
&lt;p&gt;The most controversial part of Ghostty was the private beta. Oh boy, the private beta. I vastly underestimated the effect this would have on the project. Good and bad.&lt;/p&gt;
&lt;h3&gt;The Private Beta: The Good&lt;/h3&gt;
&lt;p&gt;First, why a private beta? The primary reason was to manage my own personal bandwidth. I began publicly sharing the project shortly before my first child was born. I have experience launching software and I knew the stress that comes with it. I wanted to avoid that stress and be able to focus on my family. I felt I could curate a small group of interested users to throttle issues, only increasing the user base as I felt comfortable.&lt;/p&gt;
&lt;p&gt;A secondary, but much less important reason was that I felt a terminal emulator more than most software either works or it doesn&#39;t. You can&#39;t ship a terminal emulator that emulates only half the features Vim needs. You can&#39;t incrementally improve a terminal emulator below a certain threshold of functionality. I wanted to ensure that the first public release met that threshold. I wanted people to be able to use it productively and professionally right away.&lt;/p&gt;
&lt;p&gt;On these two fronts, the private beta was a &lt;em&gt;massive success&lt;/em&gt; (far beyond my expectations). I was able to manage my bandwidth and focus on my family. For the first year of my daughter&#39;s life, I woke her up every morning, I played with her every wake window, I went to every doctor&#39;s appointment, I was there for every milestone &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-1-0-reflection#user-content-fn-2&quot;&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And the beta community was &lt;em&gt;amazing&lt;/em&gt;. They were patient, they were understanding, they were helpful. They found bugs, they provided feedback, and &lt;strong&gt;they made Ghostty so much better&lt;/strong&gt; than it would have been without them. I can&#39;t thank them enough.&lt;/p&gt;
&lt;h3&gt;The Private Beta: The Bad&lt;/h3&gt;
&lt;p&gt;Everything else was an unintended consequence of the private beta. The hype, the frustration from those who couldn&#39;t get in, the elitisim perceived by some, etc.&lt;/p&gt;
&lt;p&gt;I didn&#39;t anticipate the hype. Some people think I am lying when I say this. I&#39;m not. I&#39;m not so naive to think that private betas and exclusive access don&#39;t generate hype &lt;em&gt;in principle&lt;/em&gt;. But I didn&#39;t think many people at all would be interested in a terminal emulator. I thought I was building boring software for a niche audience. No hype!&lt;/p&gt;
&lt;p&gt;But I was wrong, and the consequences were real. People were frustrated that they couldn&#39;t get in. People felt left out. People felt like I was being fake to generate hype. The waitlist grew larger than I was comfortable allowing in (given my prior stated priorities).&lt;/p&gt;
&lt;p&gt;I&#39;m sorry about that. All I can say is that I didn&#39;t intend for this to happen. I ramped up beta invites to try to get as many people in as I felt comfortable with (well, a bit beyond that). We ended the beta at around 5,000 users in a Discord of 28,000 at the time. Not quite the percentage of access I wanted for people but more than I could handle.&lt;/p&gt;
&lt;p&gt;The good news is Ghostty is now public and everyone can use it! I hope we can put the negative aspects of the private beta behind us and focus on the future.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One more negative aspect of the hype is the expectation of Ghostty being revolutionary.&lt;/strong&gt; It is and it isn&#39;t. Ghostty has different goals and tradeoffs than other terminals. For those looking for those properties, Ghostty is a breath of fresh air and does things that no other terminal does. But for others, it&#39;s just a terminal. And that&#39;s okay. I hope you find a terminal that works for you and I don&#39;t claim that Ghostty is the end all be all of terminals.&lt;/p&gt;
&lt;h2&gt;The Tech Stack&lt;/h2&gt;
&lt;p&gt;Ghostty has an unconventional tech stack and architecture: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ziglang.org&quot;&gt;Zig&lt;/a&gt; for the core and platform-specific code for the GUIs. More details on the tech stack can be found in the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-and-useful-zig-patterns&quot;&gt;original talk I gave on Ghostty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two years later, I&#39;m extremely happy with the tech stack and architecture. I think it was the right choice for the project but more than that, I think this architecture could be the right choice for many projects &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-1-0-reflection#user-content-fn-1&quot;&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Zig has been an absolute joy to work with. I have &lt;em&gt;fun&lt;/em&gt; writing Zig every single day and that fun hasn&#39;t diminished a bit over two years. The build system is fantastic, the community is amazing, and the core maintainers are brilliant. My excitement is well known and I&#39;ve also put my money where my mouth is by &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/zig-donation&quot;&gt;sponsoring the Zig Software Foundation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The split between shared core written in Zig and platform-specific GUIs has also been a success. I&#39;ve been able to effortless integrate &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://gpanders.com/blog/ghostty-is-native-so-what/&quot;&gt;platform-native experiences&lt;/a&gt; into Ghostty while keeping over 90% of the code shared.&lt;/p&gt;
&lt;p&gt;The beta community has also had no problem contributing to the project. I think this shows that despite being a new, niche language, Zig is easily approachable and productive for new contributors. This is great news for the future of the project.&lt;/p&gt;
&lt;p&gt;The community that formed around Ghostty was unexpected and amazing.&lt;/p&gt;
&lt;p&gt;To be specific, I&#39;m talking about the private beta community. The public community is a bit of a wild west due to the unstructured nature of not having access to the software. But the private community has focus and purpose and it has been a joy to be a part of.&lt;/p&gt;
&lt;p&gt;The Ghostty community is a group of people who are passionate about terminals (of course!) but also about performance, design, software quality, &lt;em&gt;doing things right&lt;/em&gt; and for the right reasons. It&#39;s a group of people that are pleasant to be around and collaborate with. It&#39;s a group of people that I&#39;ve really enjoyed getting to know and I&#39;m excited to continue to work with.&lt;/p&gt;
&lt;p&gt;For me personally, it&#39;s also unlikely because most people in the Ghostty community are also &lt;em&gt;new to my work&lt;/em&gt;. Most don&#39;t have a background in infrastructure and therefore aren&#39;t familiar with my previous work. It has been fun to build a new community and it reminds me a lot of the early days of my career when I was building a community around my first open source project.&lt;/p&gt;
&lt;h2&gt;A Post-1.0 Future&lt;/h2&gt;
&lt;p&gt;Ghostty 1.0 is out but the project is far from done for me. Even the 1.0 is less complete than I would have liked! But I had to draw a line in the sand somewhere and I think the 1.0 turned out great.&lt;/p&gt;
&lt;p&gt;For the short term, I want to continue focusing on the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-is-coming&quot;&gt;&amp;quot;best existing terminal&amp;quot;&lt;/a&gt; goal. I want to shore up the features that didn&#39;t make it into 1.0 and fix the bugs that are still present. I want to make sure that the foundation that Ghostty is built on is solid for future innovation.&lt;/p&gt;
&lt;p&gt;In the medium but not too long term (within 2025), I want to shift focus to release a stable and standalone &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://ghostty.org/docs/about#libghostty&quot;&gt;&lt;code&gt;libghostty&lt;/code&gt;&lt;/a&gt; that can be used by other projects to build their own terminal emulator frontends. I think &lt;code&gt;libghostty&lt;/code&gt; is the most promising part of the project for the long term impact Ghostty can have within the tech community.&lt;/p&gt;
&lt;p&gt;But, regardless of goals, Ghostty will always first and foremost be a passion project for me. I work on this project because it&#39;s fun and I want to make sure the community and I don&#39;t lose sight of that.&lt;/p&gt;
&lt;h2&gt;Thanks&lt;/h2&gt;
&lt;p&gt;I believe nothing in this world is accomplished alone. I may have started Ghostty alone but I didn&#39;t finish it alone and I have so many people to show appreciation for along the way.&lt;/p&gt;
&lt;p&gt;The impact of the Ghostty beta community cannot be overstated. I said this already in this post but Ghostty is only as feature rich and polished as it is thanks to the attention to detail and feedback of the beta community. I&#39;m extremely grateful to everyone who participated in the beta and helped make Ghostty what it is today.&lt;/p&gt;
&lt;p&gt;The Discord mods deserve a special shoutout. They worked &lt;em&gt;hard&lt;/em&gt; to keep the peace and keep the community focused on the right things. I could see their effort daily in trying to create an environment that allowed me to focus and the beta community to thrive. We have less than 10 mods for 30,000 people and they did an amazing job given the circumstances &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-1-0-reflection#user-content-fn-3&quot;&gt;3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And finally, and most importantly, I want to thank my wife. We&#39;re first time parents and juggling a new baby with a side project I wanted to find time to work on was no small feat (even with the private beta aspect). She helped me find the time to work on Ghostty and she patiently listened (fell asleep) to my ramblings about font rendering.&lt;/p&gt;
&lt;p&gt;I hope you enjoy Ghostty 1.0 and feel the love and care that went into it. I&#39;m excited to see where we go from here!&lt;/p&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I had to leave for a couple weeks over the first year so I did miss some days, but I was as present as I could be. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-1-0-reflection#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&#39;m not saying I invented it! But it isn&#39;t something I see often. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-1-0-reflection#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I will acknowledge that the mods and I wielded the ban hammer liberally. I&#39;m sure we kicked out some good people. But given the size of the Discord we had very little patience for people who acted out. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mitchellh.com/writing/ghostty-1-0-reflection#user-content-fnref-3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
</feed>
