Skip to content

node_modules FOD hash is calibrated against one bun version — breaks with any other #49063

Open

Description

@jerome-benoit

Description

The Nix flake's node_modules fixed-output derivation is hash-pinned via nix/hashes.json. Those hashes are calibrated by the nix-hashes CI workflow, which runs with the bun provided by the flake's own nixpkgs input.

Downstream consumers that follow a different nixpkgs (e.g. via inputs.nixpkgs.follows = "nixpkgs" in their flake) build the FOD with a different bun version. bun install resolves and lays out node_modules differently per bun version, so the output hash changes and the FOD fails deterministically:

hash mismatch in fixed-output derivation 'opencode-node_modules-1.18.31+a74c472.drv':
  specified: sha256-FY/I7zxmWA4tMvFZG5WijdqBcDc0No3a/YmKuxlluNg=   (bun 1.3.13, upstream CI)
     got:    sha256-Ppc2Kgb9D9xdkrNMyQgPS6rn/zU5zMqMKvAmrFCj1zQ=   (bun 1.4.2, follower's nixpkgs)

Verified to be deterministic, not environmental: building the same FOD separately with bun 1.3.13 and bun 1.4.2 yields exactly those two different hashes.

This means every follower that pins its own nixpkgs hits this class of failure each time either the upstream bun or theirs moves — see also #47332 (dev-shell bun drift) and #37623 (prior occurrence of a stale hash).

Steps to reproduce

From a flake with inputs.opencode.url = "github:anomalyco/opencode"; inputs.opencode.inputs.nixpkgs.follows = "nixpkgs"; where the follower's nixpkgs ships bun ≠ 1.3.13:

nix build .#packages.<system>.opencode

Suggested directions

Two complementary options:

  1. Short term — calibrate against more than one bun. Record hashes.json per bun major version (system × bun), or pin the FOD's bun to upstream's nixpkgs version explicitly so the calibration stays valid regardless of the follower's nixpkgs.
  2. Structural — replace the single recursive FOD with a lockfile-aware fetcher (e.g. bun2nix, which fetches packages individually using the integrity hashes already in bun.lock). That makes the FOD bun-version-agnostic by design — the current pain point generalizes to every bun bump, and bun2nix eliminates exactly this class of failure.

Happy to contribute the calibration-side change if agreed.

OpenCode version

dev at a74c472 (nix package, flake consumer)

Activity

  1. changed the title [-]fix(nix): node_modules FOD hash is calibrated against one bun version — breaks with any other[/-] [+]node_modules FOD hash is calibrated against one bun version — breaks with any other[/+] on Sep 14, 2026
  2. jerome-benoit commented on Sep 28, 2026

    @jerome-benoit
    ContributorAuthor

    Adding a data point from a consumer that hit this on v2, because it narrows the diagnosis: the hash can differ even when the bun build is identical, so "pin the bun version" alone does not make hashes.json reproducible.

    Setup: v2 at dd786c62, consumed from a flake with

    opencode = {
      url = "github:anomalyco/opencode/v2";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    so the FOD builds with the consumer's nixpkgs bun, and that resolves to the same store path as opencode's own locked nixpkgs:

    root nixpkgs (419fe0f4)  bun: /nix/store/qkydg77xf2s395md9fq0a6djjpq7fzh4-bun-1.4.2
    opencode nixpkgs (3181085b) bun: /nix/store/qkydg77xf2s395md9fq0a6djjpq7fzh4-bun-1.4.2
    

    Same bun binary, same bun.lock, same fileset source, same nixpkgs version string — and still two different results:

    $ nix build .#packages.x86_64-linux.node_modules_updater      # fakeHash, built locally
    error: hash mismatch in fixed-output derivation '...opencode-node_modules-2.0.18+dd786c6.drv':
           specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
              got:    sha256-aVE36jaHfkvtzMyQv8ssMovGMHHgLKDk0GyxYeMOkVU=
    
    $ nix build .#packages.x86_64-linux.default                   # committed hash
       → resolves from cache.nixos.org, builds fine
    
    $ nix/hashes.json (committed, i.e. what the nix-hashes runners produced)
       "x86_64-linux": "sha256-7DgxTpKv6ITKTom0mhlJNPQfdEjtEHK5P8uyCr26HZw="
    

    The local build is self-consistent — I reproduced aVE36jaHf... on separate runs hours apart — it is just not what the calibration runners produced. So the FOD output depends on something beyond the bun version: filesystem, locale, or ordering in the canonicalize-node-modules.ts / normalize-bun-binaries.ts post-processing, none of which hashes.json records.

    This is also why the nix-hashes workflow comment about native runners being required is not quite the whole story — this machine is x86_64-linux and still diverges.

    I read this as strengthening the argument for the structural option in this issue: a lockfile-aware fetcher (bun2nix, using the integrity hashes already in bun.lock) produces the same bytes on every machine, whereas re-calibrating hashes.json per consumer keeps re-introducing this. Happy to help there.

  3. added 3 commits that reference this issue on Oct 26, 2026
    643427f
    204ac21
    258a5bc
  4. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed write-up and for checking that the two bun versions give two different hashes.

    To restate the request: the Nix node_modules fixed-output derivation is calibrated against the bun in the flake's own pinned nixpkgs. With inputs.nixpkgs.follows, a different bun produces a different hash. You're asking for the dependency fetch to be independent of the bun version, either by recording hashes per bun version or pinning the FOD's bun, or by fetching from bun.lock with bun2nix. The current dev (v2) flake still works this way (nix/node_modules.nix plus one hash per system in nix/hashes.json), so this applies to v2 as well. Maintainers will consider it.

    Related issues and PRs:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions