January 17, 2023·4 min read·engineeringnixflakestoolingevaluation

Nix 2.13 lets legacy builds resolve flake sources

Nix 2.13 connects flake references to the older command interface. The change lets source resolution evolve without requiring every expression and build invocation to change together.

A move to flakes can involve several independent changes. A project may want the newer source-reference mechanism while retaining a default.nix expression and an existing nix-build invocation. Requiring all those layers to change together would couple source selection to command-interface migration.

Nix 2.13, released January 17, lets the older command interface accept flake references. The release notes illustrate direct references and references supplied through the Nix search path. The notes at the release tag provide the historical account for this version.

I value Nix because a declared set of inputs can describe a system's intended state. That makes control over source resolution especially important. This change is useful when it lets a configuration adopt a more explicit source reference without making every caller learn a new command in the same step.

Source resolution can change independently

The release includes a direct legacy-interface example. Its two source-selection and attribute-selection arguments remain distinct:

nix-build flake:nixpkgs -A hello

The flake: prefix identifies the source-resolution mechanism. Attribute selection retains the older -A hello syntax. This command therefore combines a flake reference with the existing interface for selecting a value from a Nix expression.

The legacy command reference describes nix-build as a wrapper around instantiation and realization. It evaluates expressions into derivations and then builds or obtains their results. Successful builds normally create a result symlink in the current directory.

Those familiar result-handling rules matter for scripts. Introducing the new source reference does not require describing a different way to consume the resulting output path. Existing callers can continue to use the legacy command's documented result conventions while changing the source it evaluates.

The symlink also has a garbage-collection role. According to the command reference, it registers the result as a root, and deleting or renaming it removes that root. Source-reference support leaves that documented lifetime rule in place. A build invocation therefore has both a build function and an output-retention consequence.

Search-path expressions can retain their spelling

The release notes also show a flake source behind the conventional angle-bracket lookup. This example supplies a named branch through an explicit search-path binding:

nix-build -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 '<nixpkgs>' -A hello

Here the -I argument binds nixpkgs to a flake reference. The expression still asks for <nixpkgs>, and -A still selects hello. That separates the consumer's search-path name from the external source used to satisfy it.

This is useful for a caller that already passes search-path bindings into reusable expressions. The expression can preserve the name it expects while the invocation supplies a different resolver. It is an integration mechanism, not proof that every expression has become a flake with its own declared outputs.

Quoting <nixpkgs> matters in a shell example because the brackets belong to Nix's argument, not shell redirection. The release notes quote the expression for that reason. Copying the spelling without preserving its argument boundary would change what reaches the Nix command.

A flake reference is not necessarily a fixed revision

The flake reference documentation at the tag distinguishes registry names, branch references, and explicit revisions. A short nixpkgs reference resolves through the registry. A GitHub reference can identify a repository's branch or an exact commit.

That distinction limits the reproducibility claim. Accepting flake syntax does not make an unpinned branch immutable. The example using nixos-22.05 selects a named branch. An invocation that needs a fixed source must still identify and preserve the appropriate revision through the mechanism it uses.

The documentation also distinguishes the flake reference from an output attribute in newer command syntax. In nix build github:NixOS/nixpkgs#hello, the repository reference identifies the flake and hello identifies an output attribute. The legacy examples use their own attribute-selection interface. Similar-looking package selections do not erase the difference between those command interfaces.

I regard that separation as a reason to describe the release precisely. Flakes provide a source and dependency model, but a particular reference still has resolution semantics. The value of declarative configuration depends on knowing which inputs were chosen, not simply on recognizing the word flake in a command.

Evaluation errors retain more context

The release also reworks error traces. Its notes say the traces provide more detailed explanations and more accurate locations, with a short excerpt shown by default. That is a direct improvement to the information available when evaluating a configuration fails.

For declarative configuration, the failure often appears while interpreting an expression rather than while executing the final program. A relevant location and an explanation help connect the invalid value to the declaration that produced it. The release notes promise better diagnostic information, not a different evaluation language or a guarantee that every failure becomes self-explanatory.

This is related to source selection in practical use. Once a legacy expression evaluates against a newly selected source, an incompatibility can appear through the values or functions that source supplies. Better evaluation context helps identify that mismatch. It does not establish that switching package sources is behaviorally neutral.

Removed checks are also part of the contract

Nix 2.13 removes repeat and enforce-determinism. The historical notes say they had been broken under many circumstances for a long time. Their removal matters to configurations that named them, even though it is separate from flake resolution.

Keeping a setting with an unreliable enforcement role can mislead users about what a build command establishes. The release removes those settings instead of presenting them as functioning determinism controls. A source reference and a derivation's identity still do not substitute for an independently verified claim about output bytes.

The release thus changes both integration and observability. Legacy builds gain flake source references, and evaluation failures gain more useful traces. Existing scripts can adopt the source-resolution change while retaining their command and result conventions, with the exact reference still determining which package source they evaluate.