October 6, 2025·6 min read·engineeringnixprotocolscompatibility

Nix 2.32 changes daemon compatibility and derivation JSON

Nix 2.32 raises the daemon protocol floor and removes store-directory prefixes from derivation JSON paths. The changes affect connection compatibility and machine-readable consumers through separate interfaces.

A Nix command has several interfaces beyond the arguments typed in a shell. It can communicate with a daemon, serialize a derivation, or expose values to an embedding program. Nix 2.32 changes each of those boundaries, with different compatibility consequences. The most visible are a new minimum daemon protocol and a changed derivation JSON representation.

The October 6 release notes drop support for clients and daemons older than Nix 2.0. They also change derivation JSON to use store-path basenames. These are independent changes. A working daemon connection does not demonstrate that a downstream JSON parser understands the new representation.

I value Nix because a system configuration makes its dependencies inspectable. Tooling around that configuration has dependencies too, including protocol versions and machine-readable formats. This release makes those requirements more explicit at several points where a wrapper or integration may have treated the command's output as an unchanging interface.

The worker protocol has a new minimum

The daemon worker protocol now requires at least version 18. Release notes identify Nix 2.0, released in February 2018, as the first release supporting that protocol version. The compatibility change is therefore about what the two communicating processes can speak, not about a package version selected by a flake.

That distinction matters on a machine where a client and daemon come from different update paths. Selecting a newer executable in a user environment does not necessarily replace a long-running system daemon. Conversely, updating the daemon does not establish the version of every client that may connect to it.

The notes state the removal in both directions. Older clients and older daemons fall outside the supported worker protocol. An integration needs to identify both participants when interpreting a connection failure, rather than assuming that the version printed by one shell command describes the entire interaction.

This does not mean every old derivation becomes unusable. A derivation describes a build, while the worker protocol carries communication between processes. The published change concerns that communication compatibility. Conflating it with the age of package source would make the migration sound much broader than the release notes establish.

A JSON path no longer carries the store directory

The versioned derivation JSON specification identifies the new format as version 3. Store paths in the representation now contain basenames instead of the store directory prefix. A dependency entry therefore identifies the store object without embedding /nix/store into each serialized value.

The release explains the motivation through serialization frameworks. Including the store directory requires the serializer or deserializer to receive an additional parameter representing that directory. That interferes with systematic handling of the data through ordinary framework conventions.

Separating the store directory from object basenames changes the representation's responsibility. The JSON value identifies the object within a store, while another context supplies the location of that store. A consumer that wants an absolute filesystem path must retain that distinction instead of assuming every serialized string is already suitable for opening as a local path.

This is particularly relevant to generic tooling. A graph consumer may only need to compare object identities or connect dependency entries. Requiring that consumer to treat every identity as a host-specific absolute path mixes an unnecessary storage-location assumption into the graph representation.

The format still contains information about outputs and inputs. The specification describes output names and source dependencies, with derivation dependencies identifying the outputs required from another derivation. Removing the directory prefix does not remove those dependency relationships. It changes how their store-path identifiers are serialized.

The format version is a guard with stated limits

The derivation specification says the version field must be 3. It also documents the preceding informal formats, including earlier JSON organization and the older A-Term representation. This gives a parser a visible marker for the basename change.

The same document qualifies that mechanism. While the format is experimental, version maintenance is best-effort and is not promised to identify every change. A consumer should not infer a stronger permanent schema guarantee than the specification provides merely because a numeric field exists.

The release notes say derivation JSON is the starting point for broader cleanup because the relevant commands are formally unstable and less widely used than other unstable commands. That is a scoped compatibility decision. It is not an announcement that every Nix JSON response changes to basenames in this release.

An integration consuming derivation JSON therefore needs to adapt to this particular format. A different integration using another JSON-producing command should use that command's documentation. The release's stated long-term interest does not establish that an unmentioned format has already changed.

I find the rationale reasonable for tooling that works with store objects as data. A store directory belongs to the connection or filesystem context, while an object's basename identifies it within that context. The practical cost is that existing consumers must stop assuming the JSON always includes an absolute path.

C API constness changes without changing the binary interface

The release changes nix_get_attr_name_byidx and nix_get_attr_byidx to take mutable nix_value * arguments instead of pointers to const values. Its stated reason is a more efficient internal representation of attribute-set merges, which can modify representation state on access.

The notes explicitly say this does not break the ABI. That separates source-level type checking from the binary calling interface. A source consumer may need to adjust its use of the declarations even though the function's binary interface remains compatible under the release's statement.

The optimization reduces reallocations when the right operand of an attribute-set update is small. The release reports lower total allocation for typical Nixpkgs evaluation, while keeping evaluation performance broadly similar. This is an implementation result reported by the project, rather than a guarantee about the memory limit of every system configuration.

The useful connection is that an apparently read-oriented accessor can need mutable implementation state. Constness at the C API boundary has to describe what that implementation may do. The change does not imply that reading an attribute changes the configuration's intended value.

Lazy accessors preserve unevaluated values

The C API also gains lazy accessors for list elements and attributes, including access by name and index. They retrieve a value without forcing evaluation. This matters to an embedding program that wants to forward a value into another collection or function call without demanding its result immediately.

Fetching an element and evaluating it are different operations in a lazy language. An eager accessor combines them, while a lazy accessor lets the caller preserve the distinction. The release notes identify forwarding unevaluated sub-values as a use case, making the change more specific than a general claim of faster API access.

Bounds checking improves for index-based functions as well. That addresses whether an index names a valid element, independently of whether evaluation of that element should happen now. Correct index validation and lazy evaluation solve different problems at the same access boundary.

Cache metadata can be compressed through HTTP

HTTP binary caches gain compression options for .narinfo, .ls, and build-log files. The release says uploads use Content-Encoding, allowing compatible clients to decompress transparently. This concerns cache metadata and logs, which are separate artifacts from the derivation JSON format changed earlier in the notes.

The three settings allow each kind of artifact to select its compression method. This means a cache endpoint's transport behavior can change without changing the logical content its clients consume after decompression. The compatibility condition is HTTP encoding support in the client, rather than interpreting a new derivation schema.

Another cache-related change makes nix flake check skip downloading derivations that can be substituted. The notes distinguish the previous evaluate-and-build-or-substitute behavior from the new avoidance of unnecessary downloads. A check can therefore complete with less transfer when its relevant results are already available through substitution.

Temporary build-directory names also stop including the derivation name. The release notes identify long names as a source of build failures. This changes workspace naming independently of the store-object names represented in derivation JSON.

For my infrastructure use, October's main lesson is to record which interface an integration depends on. The worker protocol floor governs process communication. Derivation JSON version 3 governs one serialized representation, and the C accessors govern embedding behavior. Each change has a defined consumer, so upgrading the toolchain and adapting the surrounding code can be reasoned about at the correct boundary.