From 923c3501d20456887867179ca947d2f98d154710 Mon Sep 17 00:00:00 2001 From: Christopher Tam Date: Tue, 9 Jun 2026 05:36:12 -0400 Subject: [PATCH 1/2] fix(nix): pin pnpm_10 through fetchPnpmDeps and pnpmConfigHook The flake's pnpm_10 override built a 10.33.2 binary, but only added it to nativeBuildInputs. Both fetchPnpmDeps (the deps-fetch phase) and pnpmConfigHook (the install phase) were silently falling back to pkgs.pnpm, which has moved to 11.x on nixpkgs-unstable. That triggers ERR_PNPM_UNSUPPORTED_ENGINE since package.json pins >=10.33.2 <11. Thread pnpm_10 into both: - pnpmConfigHook.overrideAttrs replaces its hardcoded propagatedBuildInputs = [ pnpm ] with [ pnpm_10 ]. - fetchPnpmDeps now receives pnpm = pnpm_10 explicitly (its default is pkgs.pnpm). Verified by rebuilding .#daemon.pnpmDeps and .#web.pnpmDeps against current unstable, both succeed. --- nix/package-daemon.nix | 45 +++++++++++++++++++++++++++++++++++------- nix/package-web.nix | 20 ++++++++++++++++++- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/nix/package-daemon.nix b/nix/package-daemon.nix index 2bd6f9c19f..8d5d6e151b 100644 --- a/nix/package-daemon.nix +++ b/nix/package-daemon.nix @@ -26,12 +26,18 @@ # are already wired. # # pnpm version note: -# `package.json` declares `engines.pnpm: ">=10.33.2 <11"` and pnpm -# enforces this on `pnpm install` (regardless of `engine-strict`). -# nixpkgs currently ships 10.33.0, which is rejected. The flake -# overrides `pkgs.pnpm_10` to fetch the 10.33.2 tarball from npm — -# see flake.nix for the override and how to bump the hash when -# `packageManager` advances. +# `package.json` declares `engines.pnpm` and pnpm enforces it on +# `pnpm install` (regardless of `engine-strict`). The nixpkgs +# default `pnpm` is generally incompatible with that constraint — +# it ships a version that is either older than the floor, newer +# than the ceiling, or both, depending on which nixpkgs the +# consuming flake is following. The flake overrides `pkgs.pnpm_10` +# to fetch the exact tarball pinned by `packageManager` — see +# flake.nix for the override and how to bump the hash when +# `packageManager` advances. This derivation then threads that +# pinned pnpm_10 into both `fetchPnpmDeps` (the dep-fetch phase) +# and `pnpmConfigHook` (the install phase) so neither falls back +# to the nixpkgs default `pnpm`. # # Workspace siblings the daemon depends on are built in dependency order # before the daemon itself; tsc emits each package's dist/, which is what @@ -51,7 +57,21 @@ in nativeBuildInputs = [ nodejs pnpm_10 - pnpmConfigHook + # Override pnpmConfigHook's propagatedBuildInputs to use the + # flake's pinned pnpm_10. The upstream hook hardcodes + # `propagatedBuildInputs = [ pnpm … ]` (the nixpkgs default + # pnpm attribute plus writable-tmpdir-as-home-hook and friends + # depending on nixpkgs version), which is what puts the wrong + # pnpm binary on PATH during the install phase and trips the + # package's engines.pnpm gate. Append pnpm_10 to the existing + # list rather than replacing it, so the upstream's + # writable-tmpdir-as-home-hook (which sets $HOME in the build + # sandbox) survives the override. + (pnpmConfigHook.overrideAttrs (old: { + propagatedBuildInputs = + (old.propagatedBuildInputs or []) + ++ [pnpm_10]; + })) makeWrapper # Required to rebuild better-sqlite3's native binding from source. # node-gyp drives this via Python; gnumake/pkg-config + the C++ @@ -61,9 +81,20 @@ in pkg-config ]; + # Force every pnpm invocation in this build (deps fetch + install + # phase) to use the flake's pinned pnpm_10, not whatever + # `pkgs.pnpm` happens to be on the consuming flake's nixpkgs. The + # nixpkgs default pnpm moves independently of package.json's + # engines.pnpm, and pnpm enforces that gate on `pnpm install`. + # We override pnpmConfigHook in nativeBuildInputs above to carry + # pnpm_10 instead of the default `pnpm`, and we pass + # `pnpm = pnpm_10` explicitly to fetchPnpmDeps so the dep-fetch + # derivation uses the same version (fetchPnpmDeps' default is + # `pkgs.pnpm`). pnpmDeps = fetchPnpmDeps { inherit (finalAttrs) pname version src; hash = pnpmDepsHash; + pnpm = pnpm_10; pnpmWorkspaces = pnpmWorkspaceFilters; fetcherVersion = 3; }; diff --git a/nix/package-web.nix b/nix/package-web.nix index 9de7e1691a..b49b7ff0ed 100644 --- a/nix/package-web.nix +++ b/nix/package-web.nix @@ -39,12 +39,30 @@ in nativeBuildInputs = [ nodejs pnpm_10 - pnpmConfigHook + # Override pnpmConfigHook's propagatedBuildInputs to use the + # flake's pinned pnpm_10. The upstream hook hardcodes + # `propagatedBuildInputs = [ pnpm … ]` (the nixpkgs default + # pnpm attribute plus writable-tmpdir-as-home-hook and friends + # depending on nixpkgs version), which is what puts the wrong + # pnpm binary on PATH during the install phase and trips the + # package's engines.pnpm gate. Append pnpm_10 to the existing + # list rather than replacing it, so the upstream's + # writable-tmpdir-as-home-hook (which sets $HOME in the build + # sandbox) survives the override. + (pnpmConfigHook.overrideAttrs (old: { + propagatedBuildInputs = + (old.propagatedBuildInputs or []) + ++ [pnpm_10]; + })) ]; pnpmDeps = fetchPnpmDeps { inherit (finalAttrs) pname version src; hash = pnpmDepsHash; + # Force the deps-fetch derivation to use the flake's pinned + # pnpm_10 as well. fetchPnpmDeps defaults to `pkgs.pnpm` when + # the `pnpm` arg is omitted. + pnpm = pnpm_10; pnpmWorkspaces = pnpmWorkspaceFilters; fetcherVersion = 3; }; From 747e2a4fb7356348aa20f4de7dfce71ac508fa71 Mon Sep 17 00:00:00 2001 From: Christopher Tam Date: Sat, 13 Jun 2026 08:37:12 -0400 Subject: [PATCH 2/2] fix: Clean up unused overrides --- nix/package-daemon.nix | 47 +++++++++++------------------------------- nix/package-web.nix | 16 +------------- 2 files changed, 13 insertions(+), 50 deletions(-) diff --git a/nix/package-daemon.nix b/nix/package-daemon.nix index 8d5d6e151b..8a906bb733 100644 --- a/nix/package-daemon.nix +++ b/nix/package-daemon.nix @@ -28,16 +28,15 @@ # pnpm version note: # `package.json` declares `engines.pnpm` and pnpm enforces it on # `pnpm install` (regardless of `engine-strict`). The nixpkgs -# default `pnpm` is generally incompatible with that constraint — -# it ships a version that is either older than the floor, newer -# than the ceiling, or both, depending on which nixpkgs the -# consuming flake is following. The flake overrides `pkgs.pnpm_10` -# to fetch the exact tarball pinned by `packageManager` — see -# flake.nix for the override and how to bump the hash when -# `packageManager` advances. This derivation then threads that -# pinned pnpm_10 into both `fetchPnpmDeps` (the dep-fetch phase) -# and `pnpmConfigHook` (the install phase) so neither falls back -# to the nixpkgs default `pnpm`. +# default `pnpm` is generally incompatible — older than the +# floor or newer than the ceiling depending on which nixpkgs +# the consumer follows. The flake overrides `pkgs.pnpm_10` to +# the exact tarball pinned by `packageManager` (see flake.nix +# for the override + hash bump). This derivation uses +# `pnpm_10` for both phases: in `nativeBuildInputs` so the +# install-phase `pnpmConfigHook` resolves it from PATH, and +# `pnpm = pnpm_10` to `fetchPnpmDeps` to override its +# `pkgs.pnpm` default. # # Workspace siblings the daemon depends on are built in dependency order # before the daemon itself; tsc emits each package's dist/, which is what @@ -57,21 +56,7 @@ in nativeBuildInputs = [ nodejs pnpm_10 - # Override pnpmConfigHook's propagatedBuildInputs to use the - # flake's pinned pnpm_10. The upstream hook hardcodes - # `propagatedBuildInputs = [ pnpm … ]` (the nixpkgs default - # pnpm attribute plus writable-tmpdir-as-home-hook and friends - # depending on nixpkgs version), which is what puts the wrong - # pnpm binary on PATH during the install phase and trips the - # package's engines.pnpm gate. Append pnpm_10 to the existing - # list rather than replacing it, so the upstream's - # writable-tmpdir-as-home-hook (which sets $HOME in the build - # sandbox) survives the override. - (pnpmConfigHook.overrideAttrs (old: { - propagatedBuildInputs = - (old.propagatedBuildInputs or []) - ++ [pnpm_10]; - })) + pnpmConfigHook makeWrapper # Required to rebuild better-sqlite3's native binding from source. # node-gyp drives this via Python; gnumake/pkg-config + the C++ @@ -81,16 +66,8 @@ in pkg-config ]; - # Force every pnpm invocation in this build (deps fetch + install - # phase) to use the flake's pinned pnpm_10, not whatever - # `pkgs.pnpm` happens to be on the consuming flake's nixpkgs. The - # nixpkgs default pnpm moves independently of package.json's - # engines.pnpm, and pnpm enforces that gate on `pnpm install`. - # We override pnpmConfigHook in nativeBuildInputs above to carry - # pnpm_10 instead of the default `pnpm`, and we pass - # `pnpm = pnpm_10` explicitly to fetchPnpmDeps so the dep-fetch - # derivation uses the same version (fetchPnpmDeps' default is - # `pkgs.pnpm`). + # `fetchPnpmDeps` defaults to `pkgs.pnpm`; pin to the flake's + # `pnpm_10` so the dep-fetch matches the install phase. pnpmDeps = fetchPnpmDeps { inherit (finalAttrs) pname version src; hash = pnpmDepsHash; diff --git a/nix/package-web.nix b/nix/package-web.nix index b49b7ff0ed..2f63b3824c 100644 --- a/nix/package-web.nix +++ b/nix/package-web.nix @@ -39,21 +39,7 @@ in nativeBuildInputs = [ nodejs pnpm_10 - # Override pnpmConfigHook's propagatedBuildInputs to use the - # flake's pinned pnpm_10. The upstream hook hardcodes - # `propagatedBuildInputs = [ pnpm … ]` (the nixpkgs default - # pnpm attribute plus writable-tmpdir-as-home-hook and friends - # depending on nixpkgs version), which is what puts the wrong - # pnpm binary on PATH during the install phase and trips the - # package's engines.pnpm gate. Append pnpm_10 to the existing - # list rather than replacing it, so the upstream's - # writable-tmpdir-as-home-hook (which sets $HOME in the build - # sandbox) survives the override. - (pnpmConfigHook.overrideAttrs (old: { - propagatedBuildInputs = - (old.propagatedBuildInputs or []) - ++ [pnpm_10]; - })) + pnpmConfigHook ]; pnpmDeps = fetchPnpmDeps {