Hedronite Lesson · Polyglot-Dev / Nix · Wed 2026-09-23

Nix Store Paths — Pill 18

Distinguish source / output / fixed-output paths by what each hash binds.

Lesson Class: Asr (Nix language track)
Focus: source paths · output paths · fixed-output · nix-hash · NAR · outputHash
Done-criteria: distinguish source / output / fixed-output paths
Grounding: on-disk nix-pills.epub Pill 18 · live nixos.org canonical · thesis optional second
Note: Not Pill 15 · not flakes · not install · not Python wrap · next session 14 Pills 19
Source Seal
Hash contents, seal name into the description string, land under /nix/store.
Output Predict
Out path is a pure function of the .drv input graph; name before realize.
Fixed Bind
outputHash pins the path; mirrors that share the hash share the store path.
The path is a digest of a typed description, not a free-form filename.

<!-- hal:authoritative:yaml -->

Name three path kinds. Say what each hash binds. Predict the out path from inputs before you build.

§I — Frame

Asr session 13. Thirteenth live fire of the Nix language track. Week 4 fire 3. The page is Nix Pills Nix Store Paths, Pill 18. Luca Bruno wrote the series. License CC BY-SA 4.0. Ground from the on-disk EPUB at . Live URL is canonical if the EPUB drifts: https://nixos.org/guides/nix-pills/18-nix-store-paths.html. Dolstra 2006 may sit as a second cite when you care about hashing history; the Pill is primary.

Session 12 wired packageOverrides through the fixed point. This session asks a different question: how does Nix decide the /nix/store/…-name string before the builder even runs?

Done-criteria from the syllabus: you can distinguish source paths, output paths, and fixed-output paths.

Not Pill 15 (NIX_PATH; dropped). Not flakes. Not install. Not Python wrapping. Launch a terminal when you have Nix. If Nix is not on the machine today, read the nix-repl and nix-hash numbers from the Pill and from this lesson.

§II — One recipe, three labels

Nix computes store paths in three steps, with small label differences per kind:

  1. Hash the relevant content (file, NAR, or .drv with outs blanked).
  2. Build a description string that includes type tag, hash algo, hash, store root, and name.
  3. Take sha256 of that string, truncate to 160 bits, encode base32. That digest is the store path prefix.

Musashi declarative: the path is a digest of a typed description, not a free-form filename.

§III — Source paths (named technique: Source Seal)

Yagyu names the cut once. Source Seal: copy a path into the store by hashing its contents, then seal the name into the description string.

Pill walk with echo mycontent > myfile:

$ nix-hash --type sha256 myfile
2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3
$ echo -n "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile" > myfile.str
$ nix-hash --type sha256 --truncate --base32 --flat myfile.str
xv2iccirbrvklck36f1g7vldn5v58vck

So ./myfile lands at /nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile. Same path from nix-store --add myfile. Flat hash for a regular file; recursive / NAR for directories. Relative paths in a derivation become inputSrcs after this seal.

If-then-thus: if two trees NAR-hash the same and share the name in the description, then they collide on the same store path, thus content identity is the gate and the filename alone is not.

§IV — Output paths (named technique: Output Predict)

Output Predict: the out path depends on the derivation inputs, so Nix can name /nix/store/…-foo before realize.

Simplest derivation shape from the Pill:

nix-repl> derivation { system = "x86_64-linux"; builder = ./myfile; name = "foo"; }
«derivation /nix/store/…-foo.drv»

Inspect the .drv. You already see outputs.out.path even with no build. Computation:

  1. Take the .drv with each out path replaced by the empty string (Nix’s intermediate state while naming outs).
  2. sha256 that .drv text.
  3. Description: output:out:sha256:<drvhash>:/nix/store:foo (or output:<name>:… for multiple outputs).
  4. Truncate and base32 as before.

Input .drv references inside that text are themselves replaced by the same algorithm’s hashes. The final out path is a pure function of the input graph. Builders do not pick the name; they must produce content at the predicted path.

§V — Fixed-output paths (named technique: Fixed Bind)

Fixed Bind: declare outputHash, outputHashAlgo, and outputHashMode. The out path then depends on the declared integrity hash, not on which fetchers or helpers you used as inputs.

nix-repl> derivation {
  name = "bar";
  system = "x86_64-linux";
  builder = "none";
  outputHashMode = "flat";
  outputHashAlgo = "sha256";
  outputHash = "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb";
}

The .drv records that this is fixed-output. Nix builds an intermediate string such as fixed:out:sha256:<contenthash>:, hashes that, then runs the ordinary output:out:… naming step with that intermediate hash. Result: two fetchers that promise the same tarball hash share one store path. The builder must produce content that matches outputHash or the build fails.

nixpkgs uses Fixed Bind for source tarballs. That is why transparent mirrors work: path identity follows the integrity hash.

§VI — Distinguishing table (done-criteria)

KindWhat is hashed firstDescription tagOut path depends on
SourceFile / NAR contentssource:…Content + name
Output.drv with outs blanked (and input-drv digests)output:out:… (or output:<name>:)Derivation inputs
Fixed-outputDeclared content hash (via fixed:out:… intermediate)then output:out:…Declared outputHash (+ name), not fetcher inputs

Say the table aloud once. Source Seal copies known bytes. Output Predict names build products from the input graph. Fixed Bind pins downloads and other known-byte products to an integrity hash.

§VIb — Thesis as second cite only

Dolstra 2006 may appear when you want hashing history or NAR background. Keep it secondary. The Pill’s three-step recipe (content hash, description string, truncated path digest) is enough to meet today’s done-criteria. Do not rewrite this session as a thesis seminar.

§VII — Common mistakes

  1. Treating the 32-char prefix as a random id instead of a truncated hash of a typed string.
  2. Expecting a builder to choose a different out path than the .drv already named.
  3. Confusing source seals (./file → inputSrcs) with fixed-output tarball fetches.
  4. Believing fixed-output paths still shift when you swap mirrors that share outputHash.
  5. Jumping to flakes path schemas before you can state Source Seal, Output Predict, and Fixed Bind in Pill language.
  6. Re-opening Pill 15 search paths as if they named store paths (Pill 15 stays dropped).

§VIII — Boundaries

§IX — Close

Session 12 recomposed the package set. Session 13 names what lands under /nix/store: Source Seal for inputs you copy, Output Predict for ordinary builds, Fixed Bind for integrity-pinned outputs. Next unmarked: session 14, Pills 19, stdenv.

Related:

Write three one-line definitions (source / output / fixed-output). Point at one real path on disk for each kind if Nix is present, then leave the keyboard.