Nobody Told the Packagers

On June 2nd I wrote myself a note that reads, in retrospect, like a man nailing a sign to his own front door: don’t brew upgrade ollama.

The new 0.30 release had moved all GGUF inference out of the main binary and into a separate runner called llama-server. The official tarball bundled it. The Homebrew formula did not. Neither did Arch, or conda-forge, or a half-dozen Docker images that copied the ollama binary over and called it a day. Everyone who upgraded through a package manager got a server that started cleanly, answered /api/tags, ran MLX models without complaint — and failed every single GGUF model with llama-server binary not found. Tracking: ollama/ollama#16417 , plus the Homebrew twin . I stayed on 0.23.0, where the old in-process path still worked, and waited out the wreckage. My talkie fork was already living on a custom 0.30.x build, so I got to watch the split from both sides of the glass.

And to be fair to both sides, as I wrote in that thread: this was never really an Ollama bug in the broken-code sense, and it wasn’t a brew bug either. It was a packaging gap. The formula’s go build produces the Go binary and the MLX runner; Ollama builds llama-server separately via the CMake project in llama/server/, so it never landed in libexec/lib/ollama/. The official installer shipped it, and a plain go build didn’t — which is why the same version was simultaneously fixed and broken depending on how you installed it.

A mid-century catalog photograph: a chrome engine nestled in custom-cut teal foam inside an open wooden shipping crate on a warehouse floor. The official tarball, every part in its slot.

Here’s the rub: nothing about this failure looked like a failure. The daemon was up. The tags endpoint answered, so every health check in the world reported green. If your models were MLX , nothing was wrong at all — the new binary was only needed for GGUF . So the symptom presented as: everything works except the thing you actually wanted, and the error named a binary most users had never heard of, in a sentence that read like build instructions (Run 'cmake … build …' first). Half-broken is harder to diagnose than fully broken. Fully broken gets you a stack trace and a search query. Half-broken gets you an afternoon of doubting your own models.

A pink and teal risograph print: a towering retro control panel with dozens of indicator lamps all glowing green, one dead black lamp in the middle, and a tiny figure in a lab coat staring up at the only one that matters.

My first instinct was the engineer’s instinct: paper over it. I wrote a PR that fell back to a llama-server found on $PATH — llama.cpp ships one, half the affected machines already had it sitting right there. It died in review, correctly. Per the maintainer, Ollama’s llama-server is patched for model compatibility and version-locked to the exact GGML build, so a stray binary from another build isn’t a fallback. It’s a different engine wearing the same name, and silently swapping it in would trade a loud failure for quiet wrongness. A wrong answer delivered confidently is worse than no answer at all. I have written about that shape before in a different costume.

So the PR got slimmed down to the only thing that might survive review: a clearer not-found error (ollama/ollama#16432 ). No fallback, no machinery. Just a sentence. It never merged — closed, unshipped. The cheapest fix available, words, died in the queue, and every package-manager user kept getting the cryptic one that read like build instructions.

That is the whole post, really. I went back and read the 0.30.0 release notes: “improved compatibility and performance using llama.cpp,” “support for a wider range of models, including GGUF-based models.” A real improvement, stated plainly. Now look for the line that says downstream packagers must bundle a new llama-server component or the runtime fails hard without it. That line isn’t there. If it had been, we’d have been spared half a dozen threads and a lot of people downgrading to 0.24.0. (The eventual Homebrew fix, #285963 , builds the server from the Ollama tree with the right preset — Metal on Apple Silicon. The code decisions were sound. Only the announcement was missing.)

Upstream renamed where the engine lives. The downstream packagers — the people who actually convey the software to its users, who read neither repo’s commit log over morning coffee — were never told in a form they could act on. The interface between two projects isn’t the function signature. It’s the changelog entry written for the stranger. Every dependency you take is a communication channel you’re now responsible for, and this one went quiet at exactly the moment it moved.

De-evolution usually arrives wearing progress. A cleaner architecture — inference out-of-process, one runner binary, proper separation — and the cost of the renovation was a sentence nobody wrote. The build broke where the docs weren’t, and the fix never shipped as code or as a sentence either. Say what you moved, where it went, and who has to carry it. That is the entire discipline, and it fits in a changelog line. No fingers pointed — just a slightly ironic lesson to take from a project whose entire job is to model human communication.