Using hashes for `stateVersion` instead of human-readable strings

It seems like most uses of stateVersion in NixOS fall into the following categories:

  1. Module foo has changed the default value of an option; this shouldn’t affect existing configurations.

    Practical example: The default value of virtualisation.oci-containers.backend was changed from docker to podman.

    Theoretical example: services.openssh.openFirewall currently defaults to true, unlike most instances of openFirewall, for backwards compatibility. If this were to be changed in the future, it would need to be gated by stateVersion, otherwise any existing systems relying on that default would lose SSH access.

  2. Package foo has been renamed upstream to bar. It should continue to be available to as pkgs.foo in existing configurations to avoid breakage, but new configurations should start using pkgs.bar.

    Practical example: pkgs.codimdpkgs.hedgedoc

  3. Package foo has changed it’s (database format/storage directory/other state) since a previous version, and we are able to automatically run a migration script if stateVersion is old enough to be applicable.

    Practical examples: services.sourcehut, services.timesyncd

  4. Package foo cannot be updated automatically. Perhaps it must be incrementally updated to each major version, or may require manual migration steps when upgrading. Unless the user specifics a specific package version, we must continue to install the same version by default for a particular stateVersion. If that version is no longer available in Nixpkgs, we need to throw an error with migration instructions.

    Practical examples: Nextcloud, postgresql


While [1] and [2] should continue to work fine with an opaque monotonic counter, and may benefit from not being bound to release versions, [3] and [4] seem to rely on release versions as a point of synchronization, and I’m unsure how an

There are positives to this proposal. It would allow for predictable conditions on a stable release, while not tying every stateVersion change to a release. However, it doesn’t address the original motivation of this thread, and it’s unclear how beneficial it would be over the current solution.

While I personally don’t have any interaction with third-party modules, I do agree there is utility in having a release-based stateVersion, as modules (third-party or otherwise) can easily include stateVersion conditionals without needing to coördinate to increment the current default stateVersion.

I don’t think it is accurate to assume we can drop most uses of stateVersion. Even if we no longer offer old versions of packages, or no longer include migrations, we should still throw on an old stateVersion rather than allow things to silently break with no explanation or instructions.

It is also an implementation detail of Nixpkgs, which may have effects on other projects in the Nix ecosystem.

I am not yet convinced that stateVersion being associated with a release version is a bad thing; however, I completely agree that stateVersion being visually identical to a release version is bad and confusing to many users.


If we only wish to make the value opaque to avoid confusion, we could define a new option that takes an encoded value for stateVersion, and stateVersion itself would default to a function that decodes this value.

As a trivial example, suppose on install we set a new option stateCode to a simple character translation (0123456789.abcdefghijk) of stateVersion.
e.g. system.stateCode = "cekaf"; (“24.05”)
A Nix function would reverse this transposition to set stateVersion at eval, to allow versionBefore and versionAtLeast to continue working as before.

3 Likes