Why does `builtins.getFlake` only pick up updates after a second `nixos-rebuild`

Hi

I’m trying to understand what I’m doing wrong with my current setup.

For now, I’m using a mostly “classic” NixOS configuration while gradually introducing flakes. I’d like to spend some time understanding the trade-offs and the controversy around flakes before fully committing to them.

I’m importing flakes using builtins.getFlake

The issue I’m seeing is that packages pulled from the flake are sometimes not updated correctly when I run: sudo nixos-rebuild switch --upgrade -I nixos-config=/home/myuser/.dotfiles/nixos-config/configuration.nix

Strangely, running nixos-rebuild a second time seems to always update the package to the latest version. This package has been automatically updated in the past, which makes the behavior even more confusing.

My understanding is that --upgrade only updates channels. Because of that, I’m struggling to explain the behavior I’m seeing.

Since this is not a flake-based configuration, I don’t have a flake.lock file and therefore cannot use nix flake update . My expectation was that builtins.getFlake would resolve and update independently of channel updates, but that doesn’t seem to match what I’m observing.

One theory is that a channel update (when pointer change occurs) somehow causes Nix to refresh whatever cached information is associated with builtins.getFlake.
That would explain why the package appeared to update automatically in the past.
However, it doesn’t explain why the package now seems to require a second nixos-rebuild before the update is picked up, nor why --upgrade appears to influence the result at all.


Here’s my setup hopefully that illustrates how things are configured

Setup

configuration.nix

{
config,
lib,
pkgs,
…
}:

{
imports = \[
...
./users.nix
\];

...

nix.settings.experimental-features = \[
“nix-command”
“flakes” # default registry https://github.com/NixOS/flake-registry/blob/cb70c9306b44501de412649c356dee503a25f119/flake-registry.json
\];

...

users.nix

{
  config,
  lib,
  pkgs,
  ...
}:

let
  home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz;
in
{
  imports =
    [
      (import "${home-manager}/nixos")
    ];

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.myuser = {
    isNormalUser = true;
    extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
  };
  home-manager.users.myuser = import ./users/myuser.nix;
}

myuser.nix

{ pkgs, ... }:

{
  imports = [
    ./manga.nix
  ];

...
  
  home.stateVersion = "25.11";
}

manga.nix

{ pkgs, ... }:

let
  moku = builtins.getFlake "github:moku-project/Moku";
in
{
  # WEBKIT_DISABLE_DMABUF_RENDERER
  home.packages = [
    moku.packages.${builtins.currentSystem}.default
  ];
}


Part of my confusion is that I could have imported the application using builtins.fetchTarball instead of builtins.getFlake . In both cases there is no local lock file, so I’m trying to understand what determines when Nix refreshes the remote source.

When using:

builtins.getFlake "github:moku-project/Moku"

with no explicit revision, tag, or lock file:

  • When is the GitHub repository re-fetched?
  • Is there a cache TTL involved?
  • Is there a way to force a refresh?
  • Should nixos-rebuild --upgrade have any effect on this process?

Am I misunderstanding how builtins.getFlake is expected to behave in a non-flake configuration?

Yes, there is a ttl involved. You can force a refresh with --refresh, which will get passed through from nixos-rebuild to nix.

Understand that a getFlake call like this isn’t even possible from nix code that’s inside a flake already, as it’s non-reproducible. I recommend against this kind of thing in general, since it doesn’t give you control over the update process. (The same issue applies to your invoking of home-manager from a fetchTarball call, as well.)

You can avoid these problems with pinning systems of one sort or another. Flakes offer a built-in pinning system if your configuration is a flake, but there are other pinning systems as well. However, I very much recommend you stop using unpinned fetchers in your nix code. It gives you pretty much no control or reproducibility.

2 Likes

It only updates one specific channel, actually; updating all channels requires --upgrade-all.

If you don’t want to use flakes yet, at least use npins to pin your inputs.

Thank you @tejing and @waffle8946 for your quick answers.

After some trial and error, I managed to figure things out.

@waffle8946 I initially tried using npins to pin dependencies, but I wasn’t happy with how it turned out. In the end, I switched to flakes instead.

@tejing I also wanted to note that --refresh didn’t appear to trigger a re-fetch. However, given what I describe below, I’m no longer fully certain that was actually the case.


Initially, after moving to flakes, I couldn’t successfully run rebuild switch due to a breaking change affecting dbus. This led me to use rebuild boot instead.

While troubleshooting, I noticed something odd: the system generations created by the rebuild were not appearing in the boot entry list, even though they were present under ls -l /nix/var/nix/profiles/system-*.

It then turned out that I had actually been booting from the wrong disk for the past few weeks.

This went unnoticed because I’m using a ZFS rpool, and the new pool had been renamed to match the old one. As a result, the boot partition on the old disk was still able to point to the “new” pool at boot, and everything appeared to work normally.

I’m not entirely sure this explains why I had to run rebuild switch twice, but it does explain why I was losing progress after running it (moku was reverted to older version).

Hopefully this helps someone else in the future.

Thanks again for the help!

That’s subtly wrong, as the docs you link to say:

In addition to the nixos channel, the root user’s channels which have a file named .update-on-nixos-rebuild in their base directory will also be updated.

In other words, it will update all channels (added by the root user) that happen to have such a file. nixpkgs includes it in its channel tarballs, so any nixpkgs channels will be auto-updated; other channels might also add this, not sure if this applies to home-manager.