Nix Evaluation Phases and Lazy Evaluation Impact on Reproducibility
Lazy evaluation lets Nix defer work to scale, but correctness errors can hide until triggered.

Nix's reproducibility claim rests on one architectural decision: evaluation and realization are strictly separated, so a build's identity is fixed before a single instruction runs. Everything the system promises, and everything it does not, follows from that ordering.
What the Two-Phase Pipeline Does
Lazy evaluation is the mechanism that lets Nix defer work until it is actually needed, which is what makes the two-phase model scale to a repository the size of Nixpkgs without resolving every package on every evaluation. The first is evaluation: Nix reads a .nix expression and turns it into a derivation, a .drv file that spells out the exact source, the dependencies, the toolchain, and the environment variables a build will need. Nothing gets compiled during this phase. The derivation is just a description, but it's a complete and exact one, and it carries a cryptographic hash computed from everything inside it, down to the precise versions of program sources and every dependency the build will touch.
What happens next is the detail most people skip past: the output path in the Nix store, the location where the finished build will eventually live, gets calculated during evaluation, before any build instruction has executed. That means the address of a result is decided by what goes into it, not by what a builder happens to produce when it runs. This is the opposite of how most build systems work, where you build first and name the result after.
Only once a derivation is fully written out does Nix move to the second phase, realization, where it actually executes the build instructions, inside an isolated environment built from what the .drv declared. Because that environment is constructed from a hash-addressed description rather than assembled fresh from whatever happens to be on a given machine, a realization on any machine starts from the same declared state. That's the basis for Nix's claim that builds behave consistently across machines.
The consequence of fixing the address before the artifact exists is the immutable Nix store. Build results are placed at unique, hash-derived paths, and because the address exists before the artifact, artifacts cannot silently overwrite each other, which is what makes atomic upgrades, instant rollbacks, and multiple versions of the same package coexisting on one system possible without the dependency conflicts that plague more conventional package managers. The rest of this piece, the scaling argument, the reproducibility claims, the supply chain tooling, all depend on this ordering holding.
How lazy evaluation makes a 130,000-package repository tractable
The two-phase model only scales because Nix doesn't evaluate everything it could evaluate. It evaluates only what a given configuration actually needs, deferring the rest indefinitely. That deferral, lazy evaluation, is what lets the two-phase pipeline work across a repository the size of Nixpkgs without grinding to a halt trying to resolve every package on every run.
The laziness appears in a few concrete places in the Nix language. An if-else branch that isn't taken is never evaluated. A function argument that the function body never references is never evaluated, even if it's passed in. Attribute sets can even contain definitions that refer to themselves, and Nix won't complain about the apparent infinite recursion unless something actually tries to access that self-referential attribute.
Applied to package options, this means a given package in Nixpkgs only gets evaluated at the moment it's actually being installed into someone's configuration. That's the mechanism that lets a repository carrying more than 130,000 packages avoid resolving all of them on every single evaluation run. Without it, the two-phase pipeline would need to evaluate the entire dependency graph of Nixpkgs every time anyone built anything, which would make the whole system impractical at this scale.
Laziness comes with a cost, though, and that cost appears as a correctness risk rather than a performance one. Because evaluation is deferred, a configuration error can sit dormant, invisible, until the exact moment someone enables the feature that would have triggered it. A regression filed against nixvim (issue #3788) shows the failure mode with unusual clarity: package options for a specific language server were being evaluated strictly, so they ran even when that language server wasn't enabled. Users who had never asked for that LSP were getting evaluation errors from it anyway, which breaks the exact invariant laziness is supposed to guarantee: that you only pay for what you use. The fix proposed in that issue, testing evaluation against an empty pkgs = {}, points at the right discipline here. Laziness has to be tested for directly, not assumed to hold just because the language supports it.
What the Pipeline Guarantees About Reproducibility
The single most important distinction in how Nix is discussed, and the one most often collapsed into something simpler than it is, concerns what "reproducible" actually refers to. Nix guarantees that the build environment is reproducible: the same source, the same dependencies, the same toolchain, and the same recipe can be reconstructed on any machine, at any later point. It does not guarantee that the artifact coming out of that environment will be bit-for-bit identical every time it's built.
The same pinned source and the same pinned compiler can still produce two different binaries if the compiler, the linker, or the build scripts themselves introduce non-determinism, embedded timestamps being the most common culprit. Bit-for-bit output reproducibility depends on the determinism of the underlying toolchain itself, and Nix's control over the environment doesn't reach into that layer.
The environment guarantee, for what it actually covers, holds up remarkably well. Research from Télécom Paris presented at ICSE-NIER 2024 rebuilt packages from a Nixpkgs revision six years old and succeeded 99.94% of the time. That's a strong, durable result, and it says something real about how well the evaluation model preserves a build environment across years of ecosystem churn.
Additional failure modes sit outside what Nix's architecture can reach, documented rigorously as of March 2025. Fixed-output derivations depend on a remote resource still being reachable at build time, so a URL that goes dark breaks the ability to reproduce that build's success, no matter how carefully the hash was pinned. Builds that pull from randomness without a fixed seed can succeed on one run and fail on the next, for reasons that have nothing to do with Nix. Builds that check available memory, disk space, or CPU performance can pass comfortably on one machine and fail outright on another.
Underneath all of this sits a trust boundary worth being precise about. A derivation hash proves that a build is consistent with a particular Nix evaluation. It does not prove that the evaluator, the builder, or the local toolchain executed honestly. A compromised build host can alter output after evaluation finishes without the input hash ever registering a change. Understanding this boundary doesn't weaken the case for Nix, it sharpens it: the environment guarantee is genuinely strong, and knowing precisely where it stops is what lets a team build the right additional controls on top of it rather than assuming the hash alone covers them.
Input-hash integration with supply chain attestation requirements
The hash Nix computes at evaluation time is exactly the kind of verifiable artifact identity that software bill of materials standards, SLSA provenance, and a growing stack of regulatory frameworks are asking the industry to produce. The hash by itself doesn't satisfy those requirements; it needs tooling built around it to turn into something an auditor or a regulator can consume.
The regulatory pressure behind this comes from specific, dated requirements. The EU Cyber Resilience Act requires manufacturers to identify the components in their software and maintain ongoing vulnerability handling processes. Sonatype's State of the Software Supply Chain report estimates that 90% of organizations worldwide will fall under at least one regulatory requirement demanding evidence of software assurance. That's not a niche compliance concern anymore; it's close to universal.
Nix's evaluation model fits this need unusually well on the input side. Because dependencies are pinned at evaluation time and addressed by hash in the store, tracing exactly which version of every dependency went into a given artifact is structurally straightforward rather than something bolted on after the fact. That traceability is the substrate an SBOM actually requires.
The sbomnix toolchain, actively maintained as of September 2026, builds that bridge directly. It generates SBOMs from a Nix flake reference or a store path, includes nixgraph for querying and visualizing the dependency graph, vulnxscan for scanning that SBOM for known vulnerabilities, nix_outdated for surfacing outdated dependencies in priority order, and a provenance tool for generating SLSA v1.0-compliant attestation files for any flake or derivation. Put together with the evaluation-time hash, this closes the trust-boundary gap from the previous section in a specific way: the hash establishes what went into the build, and the SLSA attestation addresses whether the build itself ran honestly.
The ecosystem hasn't finished closing this gap yet. At FOSDEM 2026, the case was made that bridging Nix and Nixpkgs to SBOM standards is imperative for the ecosystem going forward, with a concrete approach to SBOM generation and accompanying Nixpkgs improvements proposed there. The tooling exists; getting it into standard, default workflows is still in progress. And the two halves of this problem, knowing what's in your software and knowing whether it's been tampered with, are genuinely separate. SBOMs answer the first question; Sigstore, SLSA, and in-toto attestations answer the second. Both matter, and the GhostAction supply chain attack in 2025 is the event that turned provenance verification from a line item in a compliance checklist into a live production concern.
Environment drift from unenforced pipeline guarantees
The guarantees described above only hold if the same evaluated derivation, the same .drv, carrying the same input hash, gets used from a developer's laptop through CI and into production. The moment any stage in that chain swaps in a different environment, the guarantee stops applying, regardless of how carefully Nix was used everywhere else.
The most common way this breaks: CI pipelines resolve dependencies at build time instead of consuming the locked derivation that evaluation already produced. The CI environment can then drift from the developer's local one even though both sides are nominally running Nix. The result looks like the failure Nix was adopted to prevent in the first place, tests that pass on a developer's machine and fail in CI, caused not by Nix malfunctioning but by the promotion step between evaluation and deployment being broken.
ELCA's platform engineering team offers a concrete picture of what the model looks like when it is enforced properly across infrastructure. ELCA runs multiple physical hosts on NixOS as hypervisors serving dual purposes: hosting OKD virtual machines and providing essential cluster services such as NFS storage and HTTP load balancing. That setup demonstrates the same declarative environment model holding steady from a single developer's workstation all the way up to infrastructure hosts carrying production load.
Developer onboarding is where enforcement becomes measurable rather than theoretical. Without a locked, shareable environment, new contributors spend time debugging version mismatches that have nothing to do with the actual software they were hired to work on, and someone experienced usually has to sit with them while they do it. Practitioners have reported cutting onboarding time from two days down to roughly twenty minutes once a Nix Flakes environment replaces manual setup steps; the entire process becomes running nix develop. Platform engineering metrics cited by N-iX describe the same gap from the other direction: before investment in platform engineering, full developer access takes days to arrange; after, the target is a single command.
AI agent workflows and pipeline enforcement
AI coding agents raise the stakes on all of this, because they can introduce drift that's harder to catch than anything a human developer would cause. An agent operating outside a pinned, evaluation-locked environment can quietly pick up a different compiler, runtime, or library version from one session to the next, and nothing about the agent's behavior signals that the change happened.
Hallucination makes the problem worse rather than just adding to it. AI models hallucinate Nix package names and attribute paths that don't exist, and a single wrong path at evaluation time doesn't fail until realization runs, by which point the agent has often already built further work on top of the bad reference.
mcp-nixos, documented in May 2025, addresses this directly by having the agent query live Nix data sources instead of relying on whatever the model memorized during training. It consolidates more than 130,000 NixOS packages and tens of thousands of NixOS options into two MCP tools, so an agent's package references are checked against the actual current evaluation state rather than a snapshot that may already be stale by the time the model was trained. devenv 2.0, which shipped March 5, 2026, activates environments in under 100 milliseconds, which matters for agent workflows: an agent that can spin up a correctly pinned environment near-instantly has less incentive to improvise outside the pipeline's guarantees.
Sources
- What You Need to Know About Lazy Evaluation in Nix
- Reproducibility of Build Environments through Space and Time
- Lazy Evaluation in Nix: Where Your Conditionals Go to Die | Blog
- github.com
- Does Functional Package Management Enable Reproducible Builds at Scale? Yes
- Enforce lazy evaluation of `pkgs` · Issue #3788 · nix-community/nixvim
- CS SYD - Nix does not guarantee reproducibility


