Announcing Lix 2.94 “Açaí na tigela”

Nov 18, 2025

We at the Lix team are proud to announce our fifth major release, version 2.94 “Açaí na tigela”.

This release focuses on bugfixes, quality-of-life improvements, performance improvements and started integrating Lix with the Cap’n’Proto remote procedure call runtime, in order to replace the previous bespoke implementation.

Açaí na tigela is a sweet Brazilian snack food from Pará and Amazonas, made with the frozen and mashed fruit of the açaí palm.

Lix is a Nix implementation focused on reliability, predictability, friendliness, developed by a community of people from around the world. We have long term plans to incrementally evolve Nix to work in more places, to make it more reliable and secure, and to update the language and semantics to correct past mistakes and reduce errors, all the while providing an amazing tooling experience.

Upgrading from CppNix or previous Lix versions

The upgrade procedure depends on how you installed Lix or CppNix, and is fully described in the Lix installation guide.

If you are using Lix from nixpkgs on NixOS, you just need to upgrade your nixpkgs once the upgrade pull request has passed through the build farm into your channel; no other action is required.

If you want to help us test the next version of Lix, consider running main by following the beta guide.

Changes

Lix 2.94 builds on the work from Lix 2.93 in improving the daemon and language to make room for future evolution.

This release took longer than usual due to non-trivial fallouts from the CVE mitigation plus the team taking some time off.

Here are the highlights from the release notes. This is not a comprehensive list, and we are thankful for every contributor’s hard work in making this release happen.

News from RPC

As mentioned in previous communications, Lix pursues the goal of delivering a reasonable RPC protocol that replaces the bespoke and obsolete Nix daemon protocol.

Building on top of KJ was chosen because it provides access to Cap’n Proto and gives us a well-tested RPC substrate.

Lix 2.94.0 now ships Cap’n Proto protocol definitions for build hooks and for logging.

Build hooks are used during remote builds. When Lix performs a remote build (nix __build-remote), it spawns a hook program. This hook instance is a Cap’n Proto RPC server that speaks the new protocol.

This subsystem has been the first target of the ongoing RPC work.

These changes will be mostly invisible to users. The main visible improvement is that multiple build hook processes may now wait concurrently. In the old protocol only one could wait at a time.

Flakes enters freeze period

As announced in https://wiki.lix.systems/books/lix-contributors/page/flakes-feature-freeze, Flakes-related changes are now frozen unless a core team member grants an explicit exception (mostly for bugfixes).

The Lix project has received many Flakes-related changes in the past, often driven by the CppNix project. The quality of these changes did not match the usual Lix standards and forced the core team to spend considerable effort evaluating their interactions with the existing feature set. Several inconsistency issues slipped through review. This is unsurprising because Flakes remain an experimental feature with semantics that change in practice.

Now that there are at least three separate implementations of Flakes, the Lix project cannot reasonably maintain a third flavor inside core.

The Flakes implementation in the Lix codebase has also been a recurring source of maintenance headaches.

We intend to remove Flakes from the core entirely and ship them as a plugin that is included by default.

Future Flakes improvements can then happen in that subproject without affecting Lix core.

Extracting Flakes is a 2.95.0 objective.

If you are confident with C++, please consider helping us with this migration.

Breaking changes

A significant amount of technical debt has been cleared to allow safer evolution of Lix.

Language

  • Lix strings may now contain NUL bytes

    Lix can now manipulate strings that contain arbitrary binary data, including NUL bytes. The previous behavior was inconsistent and unintentional. Examples in the release notes show where this caused incorrect behavior.

    Many thanks to eldritch horrors for this.

  • Function equality semantics are more consistent, but still bad

    Functions always compare as not equal in Nixlang except when they come from the same memory location. This optimization exists to speed up comparisons of large attribute sets and had to be extended to functions stored inside attribute sets.

    While reworking the evaluator, Lix made this behavior more consistent, although still undesirable.

    For example: let s.f = f; in s.f == s.f now evaluates to true.

    Lix intends to remove this optimization later.

    Function equality is undefined behavior and should not be relied upon in portable Nixlang code.

    Many thanks to eldritch horrors for this.

Stores and builds

  • Remove support for daemon protocols before 2.18

    This affects clients connecting to the local daemon socket or remote builders configured using the ssh-ng protocol. Builders using the ssh protocol are still supported for older clients such as Nix 2.3.

    Maintaining these older protocols required too much effort and lacked test coverage.

    Many thanks to eldritch horrors for this.

  • Remove impure derivations and dynamic derivations

    The impure-derivations and dynamic-derivations experimental features are removed.

    New impure or dynamic derivations can no longer be created. Existing ones cannot be read or built. Their outputs remain valid until garbage collected. The .drv files may only be garbage collected.

    Many thanks to eldritch horrors for this.

  • A new cgroup delegation model for the cgroups experimental feature

    Builds using cgroups (use-cgroups = true and experimental-features = cgroups) now always receive a delegated cgroup tree with permission to manage controllers in that subtree.

    This can cause visible breakage because the build process (daemon or direct store access) now requires to be run inside a cgroup tree that was already delegated by the caller: service manager or system administrator for example.

    The uid-range experimental feature now depends on cgroups.

    The release notes contain guidance on setting up the tree and working around issues if you get stuck.

    Many thanks to Raito Bezarius, eldritch horrors and lheckemann for this.

  • Fixed output derivations now run using pasta network isolation

    Following the mitigation of CVE-2025-46416, fixed-output derivations are now isolated from the host network using Pasta.

    This is a breaking change. We learned a number of operational details while deploying Pasta at scale.

    If your DNS setup is healthy (first server in /etc/resolv.conf responds quickly) and the derivation only needs TCP or UDP, this change should not affect you.

    Many thanks to eldritch horrors and puck.

  • Enable zstd with a high compression level instead of xz for binary cache uploads

    Binary cache uploads now use zstd instead of xz. This significantly improves upload time on modern systems and high-speed links, enabling gigabit link saturation while uploading to fast Garage S3 implementations.

    The release notes contain a typo: reducing runtime from 77 seconds to 18 seconds is about a 75 % improvement, not 50 %.

    On a 4.4GB NAR file, uploads can be 75 % faster at the cost of roughly 18 % larger output.

    Many thanks to eldritch horrors and Raito Bezarius.

Features

  • HTTP/3

    Lix can now fetch from binary caches using HTTP/3 when supported by both the server and the local curl stack. This can reduce latency, although throughput may vary as described in https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/.

    HTTP/3 is off by default.

    Many thanks to Raito Bezarius and eldritch horrors.

  • Experimental integer coercion

    Lix adds an experimental feature that allows integers to be coerced where strings were previously required. This reduces boilerplate but changes language semantics, so it is off by default.

    Many thanks to Raito Bezarius, eldritch horrors, delroth and winter.

  • nix-eval-jobs no-instantiate

    nix-eval-jobs now supports --no-instantiate, skipping derivation instantiation and improving performance of large evaluations.

    Many thanks to Ma27 and mic92.

  • Hyperlinks in attribute set printing

    The attribute set printer used by nix repl and in type errors now prints hyperlinks on attribute names to their definition sites when known.

    Example:

    $ nix eval -f '<nixpkgs>' lib.licenses.mit
    { deprecated = false; free = true; fullName = "MIT License"; redistributable = true; shortName = "mit"; spdxId = "MIT"; url = "https://spdx.org/licenses/MIT.html"; }
    

    Many thanks to jade for this.

Improvements

Debugging

  • Ctrl-C rework

    Interrupt handling has been improved so Ctrl-C behaves predictably across long evaluations and daemon interactions.

    One Ctrl-C requests a graceful shutdown. A second Ctrl-C aborts immediately with no guarantee of data integrity.

    ❯ nix-instantiate --eval --expr 'let f = n: if n == 0 then 0 else f (n - 1) + f (n - 1); in f 32'
     ^CStill shutting down. Press ^C again to abort all operations immediately.
     ^C
    
     ❌130 ❯
    

    Many thanks to eldritch horrors.

  • Stack traces now summarize involved derivations at the bottom

    Evaluation stack traces now end with a summary that collects derivations involved in the error, which helps identify which package triggered the failure in a dependency tripping an assertion such as unsupported, insecure or broken derivations.

    error:
         … while calling the 'head' builtin
           at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
           1700|           if length values == 1 || pred here (elemAt values 1) (head values) then
           1701|             head values
               |             ^
           1702|           else
    
         … while evaluating the attribute 'value'
           at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
           1117|     // {
           1118|       value = addErrorContext "while evaluating the option `${showOption loc}':" value;
               |       ^
           1119|       inherit (res.defsFinal') highestPrio;
    
         (stack trace truncated; use '--show-trace' to show the full trace)
    
         error: Package ‘olm-3.2.16’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
    
    
         < -snip the whole explanation about olm's CVEs- >
    
    
         note: trace involved the following derivations:
         derivation 'etc'
         derivation 'dbus-1'
         derivation 'system-path'
         derivation 'nheko-0.12.1'
         derivation 'mtxclient-0.10.1'
    

    Many thanks to Qyriad.

Debugging builds

  • --keep-failed chown the build dir to the invoking user

    When using --keep-failed or keep-failed = true, Lix now reliably changes ownership of the failed build directory to the user who requested the build, including through the daemon.

    Many thanks to eldritch horrors.

  • Keeping mismatching fixed-output derivation artifacts

    When fixed-output derivations fail because the produced output does not match the expected hash, both paths are printed. The offending output is added to the store so that you can inspect it, compute a new hash, or fetch a known-good output for comparison.

    Many thanks to lheckemann.

  • Show tree with references that lead to an output cycle

    Output cycles now include a reference tree showing exactly how the cycle arose.

    Example:

    error: cycle detected in build of '/nix/store/gc5h2whz3rylpf34n99nswvqgkjkigmy-demo.drv' in the references of output 'bar' from output 'foo'.
    
         Shown below are the files inside the outputs leading to the cycle:
         /nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar
         └───lib/libfoo: …stuffbefore /nix/store/h680k7k53rjl9p15g6h7kpym33250w0y-demo-baz andafter…
             → /nix/store/h680k7k53rjl9p15g6h7kpym33250w0y-demo-baz
             └───share/snenskek: …???? /nix/store/dm24c76p9y2mrvmwgpmi64rryw6x5qmm-demo-foo …
                 → /nix/store/dm24c76p9y2mrvmwgpmi64rryw6x5qmm-demo-foo
                 └───bin/alarm: …texttexttext/nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar abcabcabc…
                     → /nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar
    

    Many thanks to Ma27.

  • disallowedRequisites now reports chains of disallowed requisites

    Errors now include the full chain of references leading to each forbidden path rather than only the immediate offender.

    Example:

    $ nix-build -A hello
    error: output '/nix/store/0b7k85gg5r28gb54px9nq7iv5986mns9-hello-2.12.2' is not allowed to refer to the following paths:
        /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-glibc-2.40-66
        Shown below are chains that lead to the forbidden path(s).
        /nix/store/0b7k85gg5r28gb54px9nq7iv5986mns9-hello-2.12.2
        └───/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-glibc-2.40-66
    

    Many thanks to Ma27 and Robert Hensing.

Performance

  • Substituter query speed

    Substituter queries now take advantage of asynchronous work introduced in 2.93. Caches with higher latency benefit more from these improvements.

    Nonetheless, it was measured to result in roughly a 60 % reduction in query time for medium-sized closures like NixOS systems.

    Many thanks to eldritch horrors.

  • Rate limiting nix copy parallel operations

    nix copy previously hit Too many open files errors on main. We added a rate limit to avoid that. You can remove the limit by increasing the open files limit using ulimit -n <new number>.

    Many thanks to Raito Bezarius.

  • Pointer tagging, thunk state sharing and unreferenced Values

    Lix implemented pointer tagging and reduced the Value structure to a single machine word. Thunk state sharing was implemented, enabling more reuse of Value objects. Value is now used as a reference-counted smart pointer to a heap object.

    This unblocks further optimizations and resulted in:

    • 15 % memory savings and a 3 % evaluation time regression on system rebuild
    • 17 % memory savings and a 7 % evaluation time improvement on nix search

    Many thanks to eldritch horrors.

  • Reuse of special strings in the language

    Common strings that occurs in evaluation such as the result of builtins.attrNames are now reused more efficiently, reducing allocations and slightly improving evaluation speed.

    Up to 11 % memory savings were observed in large NixOS deployments with a slight decrease in CPU usage.

    Many thanks to eldritch horrors, xokdvium, Raito Bezarius, Tom Hubrecht and NaN-git for this.

  • Parallel marking in garbage collection

    The Boehm-Demers-Weiser garbage collector supports parallel marking. Lix now enables this feature.

    Contrary to the release notes, we did not observe a 38 % improvement on nix search.

    Many thanks to Eelco Dolstra and Seth Flynn for this.

Fixes

  • build-dir no longer defaults to temp-dir

    Temporary build directories no longer default to temp-dir (typically /tmp), fixing CVE-2025-46415.

    Many users use a tmpfs for /tmp. The default build directory is now /nix/var/nix/builds. If you care about tmpfs semantics, bind-mount that directory onto a tmpfs.

    Compared to 2.93.3, additional changes were made so Darwin handles the new path length correctly, which allows reasonable derivations to connect to UNIX domain sockets in sandboxes. This will also be shipped in 2.93.4.

    Many thanks to eldritch horrors, Emily and Qyriad for this.

  • Global CA are copied inside the builder environment

    Global CA bundles are now correctly propagated to the builder environment. This fixes TLS verification issues for fetchers relying on system CAs.

    This adds about 500 KB per fixed-output build in scratch locations.

    Many thanks to Raito Bezarius and Emily for this.

  • Exponential backoff for downloads

    In 2.93, we lowered the connect-timeout to 5 seconds. Some users have DNS setups where the first nameserver times out, causing resolution to exceed 5 seconds.

    We replaced linear backoff with exponential backoff to handle these cases more robustly.

    Many thanks to Ma27 for this.

  • nix develop for derivations that reject dependencies with structured attributes

    Recent work in nixpkgs for bash-less systems added many outputChecks.<output>.disallowedRequisites definitions to packages such as systemd.

    This caused nix develop nixpkgs#systemd to stop working, which is a common workflow for systemd development.

    The root cause was that nix develop only understood non-structured output checks.

    Lix 2.94.0 adds support for structured output checks, so nix develop nixpkgs#systemd works again.

    nix-shell is unaffected by this problem.

    Many thanks to Raito Bezarius for this.

  • “My shell didn’t work” — nix-shell default shell directory is not /tmp anymore

    Historically, nix-shell stored internal shell files in $TMPDIR or /tmp and also used it for $NIX_BUILD_TOP. Many users have $TMPDIR unset, so /tmp was consistently used.

    If you ran sudo nix-shell and exited uncleanly, you could create /tmp/env-vars with root permissions, causing all subsequent shells for unprivileged users to fail silently. The workaround was to delete the file manually.

    Lix now creates a dedicated temporary directory for shell metadata that does not collide with other shells. Cleanup is handled by Lix itself after the shell exits.

    Many thanks to Raito Bezarius for this.

  • Remove reliance on bash for remote stores via SSH

    Lix 2.93 changed SSH remote store handling in a way that broke classical ForceCommand and similar directives. We reverted the problematic part in 2.93.1 and carry the same fix here.

    Previous configurations work again out of the box.

    Many thanks to Raito Bezarius for this.

You can read the full changelog in the manual.

Known issues

The release notes may contain imprecisions and typos, we are working to correct this without doing a point release.

No impactful known issues are yet known!

Credits

Thanks, as always, to the following groups:

  • The large community who beta tested the upcoming release by running main in production since the 2.94 branch-off. We really appreciate having immediate feedback on our work and the trust of running main alongside us means a lot to us. We know we tested the patience of some of you, we thank you for that.

    If you want to run Lix main yourself, see the beta guide for details.

  • Everyone who contributed by filing bugs and giving us feedback on Matrix.

  • All the first time contributors who made their first contributions to a Nix implementation in Lix. We are eternally grateful to everyone who helped us out on the numerous important but tedious issues.

  • All the contributors who have helped us with the backlog of bugs.

  • The CppNix contributors and CppNix team, without whom we would not have this software, and who wrote some of the improvements ported into this release.

A quiet but heartfelt note of gratitude goes to eldritch horrors for their steady guidance throughout this release, even in the face of its many challenges.

Onwards and upwards for the next release. We look forward to continuing to work together with everyone to build a better foundation for the evolution of Nix.