February 27, 2026·6 min read·engineeringnixinstalleroperations

The Nix installer beta records how to undo an installation

Nix 2.34 introduces an upstream Rust installer beta derived from the Determinate installer. Its plan and receipt model makes installation state explicit and supplies an uninstall operation, including for installations adopted from the shell installer.

Installing a package manager requires changing a machine before that manager can describe its own environment. Nix 2.34, released today on February 27, introduces a Rust installer beta that gives those changes an explicit plan and recorded state. The release announcement also provides an uninstall operation. That combination addresses the host setup around Nix, where a declarative package model alone cannot account for every installation effect.

The upstream installer is a modified version of the Determinate Nix Installer. The release credits that project and directs problems with the new beta to the NixOS installer repository. That ancestry matters because upstream adoption builds on existing installation machinery. It does not make the upstream and Determinate distributions interchangeable products.

Nix manages the desired state of my personal infrastructure, and that makes installation ownership interesting to me. A reproducible package description is useful after a machine can evaluate it. The first installation still has to establish the conditions under which the package manager runs. An explicit record of that work is a practical extension of the same preference for inspectable state.

Plans turn installation into named operations

The installer source at the 2.34.0 tag represents an installation as an InstallPlan. It contains the planner, a version, and a vector of stateful actions. The planner performs platform and pre-install checks before producing those actions. Installation and reversal therefore share a representation of the work to be done.

This is a stronger structure than describing success only as a final message from an installation script. Each action has an identity and associated state. The plan can describe its actions before execution, and the reversal path can use the same actions in the opposite order. That gives the implementation something concrete to serialize and inspect when the ordinary success path is interrupted.

The source explicitly makes execution of the top-level actions sequential. Actions that can run concurrently belong inside grouped actions. This preserves the meaning of the outer sequence as installation stages. Concurrency is consequently an implementation property of a defined operation, instead of an accidental consequence of independent setup commands racing one another.

The distinction matters for dependencies between host changes. An operation can depend on a prior action establishing a directory or another prerequisite. Reversing the outer sequence then follows the opposite dependency direction. The plan structure expresses that ordering without assuming that every individual action is itself a single indivisible change.

An action can be completed or deliberately skipped

The stateful-action implementation distinguishes completed, uncompleted, in-progress, and skipped actions. A completed action does not execute again through try_execute. An uncompleted action does not need reversal through try_revert. Skipped actions remain skipped in both directions.

Those states answer different questions. Completed means the action belongs to work the installer performed. Skipped can represent an operation that planning determined was already satisfied. Treating both as a generic success flag would lose information needed when uninstalling. Existing state and newly created state may require different treatment.

During execution, the wrapper sets an action to in-progress before calling its implementation and marks it completed after success. Reversal similarly invokes the action before returning it to uncompleted. This records the direction and completion state around the underlying operation. The action implementation still has to handle its own actual host effects correctly.

That final responsibility is important to the failure model. A sequence of filesystem and service changes does not become a database transaction merely because its state is serializable. An action can fail after some effects have occurred. The framework gives the installer a way to represent and revisit work, while the correctness of each reversal depends on what that action did.

The receipt preserves the plan

The source defines the receipt location as /nix/receipt.json. The plan serialization includes the action states and installer version. When installation is cancelled or an action returns an error, the code attempts to write a receipt before returning that failure. It also writes the receipt after the normal action sequence completes.

The write path serializes the record to a temporary path and renames it into place. That is a concrete publication step for the receipt file. The presence of a temporary-file-and-rename pattern should not be expanded into a claim about recovery from every possible storage failure. It describes how this implementation replaces the visible receipt.

The version is also operational information. Before acting on a saved plan, the installer checks compatibility between the recorded version and its own current version. A mismatched representation can produce a compatibility error. An uninstall record is useful only when the program reading it understands the actions and settings it contains.

The code has tests covering acceptance of a compatible version and rejection of an incompatible version. Those are tests of the receipt interpretation boundary. They make a specific promise reviewable without implying that every supported operating-system state has been exercised by those two cases.

I like this part of the design because it keeps installation history available to the program responsible for removing it. A separate cleanup guide would have to reconstruct what happened from current files and remembered defaults. The receipt instead supplies the recorded plan, subject to the implementation limits of each action and its compatibility checks.

Uninstallation reverses the recorded sequence

The beta release documents the installed uninstaller at the following path. This command removes the Nix installation, so it is an explanation of the lifecycle operation rather than a diagnostic command to run on a working system.

/nix/nix-installer uninstall

The uninstall implementation first checks compatibility and platform conditions. It then visits the plan actions in reverse order and calls their reversal methods. If a reversal reports an error, the loop records it and continues through the remaining actions. At the end it returns the collected reversal errors if any occurred.

Continuing after an error is a deliberate recovery policy. It can allow independent cleanup work to proceed while retaining evidence that some part failed. It also means a failed uninstall can leave a partially removed environment. The returned result needs to remain part of the operator understanding of the machine, rather than being reduced to an assumption that invoking uninstall removed everything.

The release notes state that the beta can run over an existing shell-script installation without adjustments. They also state that uninstalling afterwards removes the entire Nix installation, including that adopted installation. This is consequential behavior for someone evaluating the beta on an established machine. Adoption does not create a promise to restore the previous shell-installed Nix when the new installer is removed.

That ownership decision is understandable. The recorded lifecycle becomes the lifecycle of the installation the tool manages. It is still different from undoing only the executable replacement that introduced the new installer. The release notes make this scope explicit, which is more useful than leaving users to infer it from the word uninstall.

Installer behavior remains separate from package evaluation

The new beta is an installation mechanism. It does not change the meaning of a Nix package expression merely because its implementation uses Rust. The relevant improvement is the lifecycle model exposed by the installer and the implementation behind that model. The language used to implement it is one fact about the tool, not evidence that all setup failures have disappeared.

The release also points to a GitHub Action using the installer for CI. That provides another consumer of the same setup path. A persistent workstation and a disposable CI runner still have different recovery needs, but both benefit from an installer with explicit settings and recorded operations. The installation process can be studied independently of the packages a later job builds.

Cancellation follows the same recorded-state model. The outer loop checks a shared cancellation flag between actions and attempts to preserve the receipt before returning. This is cooperative cancellation at a stage boundary. It does not imply that an arbitrary host operation can be interrupted at any instruction and automatically reversed.

For an existing installation, the most important beta behavior is its scope of ownership after adoption. For a new installation, the plan and receipt provide an account of the host changes made to establish Nix. Both cases now have a documented removal operation whose implementation follows the recorded sequence and reports incomplete reversals.