Farid Zakaria

7 min read

How safe is follows?


I recently wrote about omniflake, accessible via https://omniflake.com, which indexes over 12,000 flakes and makes them available through a single flake input.

One of the earliest requests I had was to support something similar to my tool nix-auto-follows to consolidate every flake’s inputs to a single coherent set.

This is the same pattern you see often in every guide to flakes, often highlighting the follows attribute without much explanation of what it does or why it is used.

inputs.disko.url = "github:nix-community/disko";
inputs.disko.inputs.nixpkgs.follows = "nixpkgs";

The follows attribute consolidates a flake’s inputs to a single source, often used for nixpkgs. This reduces the number of distinct nixpkgs revisions in the dependency graph, which can speed up evaluation and reduce overall closure size.

Those with more experience with flakes have a strong opinion about follows. While it improves evaluation speed and closure size, it can introduce subtle breakages and deviates from the original author’s intent which is counter to the philosophy of Nix which deems “reproducibility” as a core principle.

starship troopers follows meme

I wanted to dig into the question of how safe it is to use follows in practice. The folklore is that it is dangerous, but I wanted to see if that was true. Turns out that https://omniflake.com is the perfect tool to try and answer this question.

§Time traveling Nixpkgs

As I mentioned above, follows lets you consolidate a flake’s inputs to a single source. In the case of nixpkgs, this means that you are asking a flake to build against a nixpkgs revision that its author may never have evaluated it with. This can lead to breakages if the flake relies on specific behavior or attributes of the original nixpkgs revision.

Often the delta between the two revisions is a good measure of how likely it is to break. If the nixpkgs revision you are following is only a few days or weeks newer than the original, it is likely to be safe. If it is several months or years newer, the risk of breakage increases.

We can look at every flake within omniflake and see how old the nixpkgs revision is that it locks. 11,936 flakes are indexed in the omniflake index, 10,754 of them lock a nixpkgs and between them they name 3,261 distinct nixpkgs revisions.

Here is every distinct revisions, placed by the date it was locked by the number of flakes that pinned it.

1980-01-01T00:00:00+00:00 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ 2020 2021 2022 2023 2024 2025 2026 1 3 10 30 100 300 flakes pinning this revision nixos-unstable nixpkgs-unstable nixos release small release darwin release never a channel

Quite a lot of revisions are pinned by only a single flake. We can also visualize how old each nixpkgs revision is as of August 31, 2026.

1980-01-01T00:00:00+00:00 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ 1w 1mo 3mo 1y 2y 5y 0% 25% 50% 75% 100% age of the pinned nixpkgs share of flakes this old or newer all pins nixos-unstable nixpkgs-unstable nixos release small release darwin release never a channel

Half of all flakes are pinned to a nixpkgs older than 506 days, nearly a year and a half, and a twentieth are past 1,768 days (five years).

The channels follow the distribution one would largely expect: nixos-unstable (red line) pins are the freshest, which is unsurprising for a channel that moves daily. The darwin channel seems scary to replace with follow, barely 5% of those pins are under a year old

What does this all mean?

If we consider the median, when you add a follows line you are, typically, asking a flake to replace it with a nixpkgs likely a year and a half newer than anything its author ever tested. One time in twenty you are asking it to consolidate across five years. 😬

§Which nixpkgs, though?

Not all nixpkgs are equal. A flake pinned to a nixpkgs that was a channel bump was built by Hydra and therefore received testing and is available for substitution from https://cache.nixos.org.

We can audit the nix release archive to determine whether a given nixpkgs was ever a channel bump. Every channel bump ever published is a directory under nix-releases, named for the commit it pointed at:

$ curl -s 'https://nix-releases.s3.amazonaws.com/?delimiter=/&prefix=nixos/unstable/' \
    | grep -o '<Prefix>[^<]*</Prefix>' | tail -1
<Prefix>nixos/unstable/nixos-26.11pre1064949.34ab99075ac4/</Prefix>

If we enumerate all 74 channels, every nixos/* and nixpkgs/*-darwin channel, we discover 30,850 (commit, channel) pairs to check against.11Earlier releases used shorter revisions and nixpkgs-unstable has no channel directory, its releases sit under nixpkgs/

1980-01-01T00:00:00+00:00 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ 0 1000 2000 3000 4000 5000 darwin release small release never a channel nixos release nixpkgs-unstable nixos-unstable 697 749 857 877 2,541 5,033 measure flakes revisions

The majority of revisions, four in five, were a published channel bump. That is better than I expected and means that most flakes are pinned to a nixpkgs that was built by Hydra and therefore received testing and is available for substitution from the cache. Few flakes are pinned to a nixpkgs that was never a channel bump.

The most common revisions that are shared amongst the flakes are relatively recent as well. The top 8 revisions are all from 2026, and the oldest revision in the top 8 is from May 4, 2026.

revision locked flakes channel
9fbb54b33e91 2026-08-26 302 nixos-unstable
2c423e03bbaf 2026-08-21 98 nixos-unstable
567a49d1913c 2026-06-15 72 nixos-unstable
a62e6edd6d5e 2024-05-31 70 23.11
56c02bc00adc 2026-08-23 68 nixos-unstable
549bd84d6279 2026-05-04 58 nixos-unstable
ffb3c9b700e7 2026-08-19 52 nixos-unstable
391b592eb448 2026-08-20 51 nixpkgs-unstable

§Nobody pins old nixpkgs

It was good to see that the most common revisions across flakes are relatively recent. Personally, I enjoy running a release or two behind my personal NixOS laptop to avoid unecessary churn while all the kinks are worked out. How common is that?

For each flake, we can take the gap between when the flake was last touched and the date of the nixpkgs it locks. That is how stale the pin was at the moment its author chose it, this is very different than the earlier graph which is how stale it is as of today.

1980-01-01T00:00:00+00:00 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ 1d 1w 1mo 3mo 1y 2y 5y 0% 25% 50% 75% 100% age of the pinned nixpkgs share of flakes when its author chose it today

The original stateleness is depicted by the green line. It is the age of Nixpkgs the day it was written whereas red is the same pin today. Half of every flake in the index picked a nixpkgs less than three week old, and a quarter picked one inside three days!

The shades region is simply time passing. It is how stale the pin has become over time. It is a frequency of how often a flake is updated.

  when chosen today
p25 3 days 118 days
p50 17 days 506 days
p75 126 days 1,000 days
p95 601 days 1,768 days

So essentially nobody deliberately pins an old nixpkgs. They run nix flake update, get whatever was current that week, and stop. The ecosystem’s year and a half median is the result of a year and a half of neglect on each flake.

§Did we answer anything?

Not realy but it was fun to look at the data.

omniflake includes an easy way to unify all flake inputs. We could use it to see how many flakes would break if we replaced their nixpkgs with a single revision, but that would only tell us whether it could evaluate. A flake can evaluate perfectly and then fail to build. That’s a lot harder to measure.

For now, I still recommend being mindful when you use follows. It is a powerful primitive that can improve evaluation speed and closure size, but it can also introduce subtle breakages, especially if you rely on nix-darwin. 🤫

The data for this post came from omniflake’s own index, joined against the release archive. It is one file, on a gist using a nix shebang and the standard library, so it needs nothing installed.

Beware of bugs in the above code; I have only proved it correct, not tried it – Donald Knuth