You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lix 2.95 removed builtins.fetchClosure (see #2946). #2949 added an up-front check that refuses to run on Lix ≥ 2.95 with a clear error. This issue tracks actually restoring compatibility by replacing fetchClosure in the generated flake with builtins.appendContext, which needs no experimental feature and works on both Nix and Lix.
fetch-closure experimental feature removed, and builtins.fetchClosure with it. Since Lix removed CA derivations most of the inner workings of fetchClosure have become inaccessible, and the remainder was largely not an improvement over existing features (such as importing store derivation). We've removed it to lighten the maintenance burden.
It's a knock-on effect of Lix removing content-addressed derivations. The input-addressed mode (inputAddressed = true, the only mode Devbox uses) was considered redundant. No migration path or transition period was provided.
Does this affect upstream Nix?
Not currently, and there's no indication it will:
fetch-closure is still an experimental feature in the Nix 2.34 manual; upstream (2.35.x) hasn't removed it.
Upstream still has ca-derivations, so Lix's rationale doesn't apply.
NixOS/nix#8963 (stabilize fetchClosure) is open with maintainers leaning toward stabilization; nobody is proposing removal. Determinate Nix tracks upstream.
It is still an experimental feature though, so upstream is free to change it — one more reason to move to a stable primitive.
How Devbox uses fetchClosure
internal/shellgen/tmpl/flake.nix.tmpl — for every package whose store path is in cache.nixos.org, Devbox pre-builds the path (nix build /nix/store/...), then the generated flake references it via:
The template comment already calls this a hack. All we actually need is "a string with store-path context that Nix will ensurePath", so the flake doesn't have to download/evaluate that package's nixpkgs revision.
fetchClosureExpr in internal/shellgen/flake_plan.go is dead code: addOutput only runs for Patch packages, and IsInBinaryCache is always false for those. (And patchGlibc accesses pkg.name / pkg.buildInputs, so it would break if it were ever reached.)
Alternatives considered
Option
Verdict
builtins.storePath
❌ Not allowed in pure eval on either Nix or Lix (Lix tracking issue #402, NixOS/nix#5868, both unresolved). Would require --impure on print-dev-env, which Devbox deliberately keeps off (featureflag.ImpurePrintDevEnv) because it disables eval caching and leaks env.
Path literal / path: flake input
❌ Copies the path into a new store path, losing the closure's references.
Reference nixpkgs-<rev>.legacyPackages... (the non-cached path)
❌ Works, but throws away the binary-cache optimization: every cached package forces a nixpkgs tarball download + eval.
builtins.appendContext
✅ builtins.appendContext "/nix/store/x" { "/nix/store/x" = { path = true; }; } yields a string with opaque path context, has no pure-eval restriction, calls store->ensurePath() (substitutes from configured substituters if missing — same behavior as fetchClosure), needs no experimental feature, and has existed since Nix 2.0.
Verification
Built Devbox from main, devbox add hello@2.12.1, took the generated flake, and swapped the fetchClosure block for:
Nix 2.30.2, experimental features nix-command flakes only, pure eval: nix print-dev-env --json returns buildInputs = /nix/store/zq74…-hello-2.12.1, hello on PATH. ✅
Substitution parity:appendContext on a cowsay path not in the local store pulled perl + cowsay from cache.nixos.org during eval. ✅
Lix 2.95.2 (nixpkgs-unstable#lixPackageSets.latest.lix), using Devbox's exact flags: the original flake fails with attribute 'fetchClosure' missing; the appendContext flake produces the identical dev env. ✅ (Lix also prints warning: unknown experimental feature 'ca-derivations' / 'fetch-closure' — non-fatal.)
mkShell's buildInputs accepts plain strings (checkDependencyList allows string), which the test confirms.
Plan
Replace fetchClosure in internal/shellgen/tmpl/flake.nix.tmpl with builtins.appendContext, keeping the builtins.trace "downloading …" wrapper and replacing the HACK comment with one explaining the context trick and that the path is pre-built by Devbox (and substituted on demand otherwise). This is the whole fix.
Delete dead fetchClosureExpr and the IsInBinaryCache branch in glibcPatchFlake.addOutput (internal/shellgen/flake_plan.go). Update the stale comments in internal/shellgen/flake_input.go and internal/devpkg/narinfo_cache.go.
Drop fetch-closure from the experimental-features flags in internal/nix/command.go, internal/nix/nix.go (ExperimentalFlags), and .github/workflows/cli-tests.yaml. Nothing else uses it.
Relax the Lix gate from fix(nix): detect incompatible Lix and print a clear hint (#2946) #2949 in internal/nix/install.go: remove the SupportsFetchClosure check, LixVersionWithoutFetchClosure, and their tests. Keep Info.Implementation / IsLix() — the parsing is correct and useful for future diagnostics.
Test: unit tests for the generated flake (assert appendContext appears, fetchClosure doesn't), plus manual runs with Nix and Lix 2.95.2 as above. CI's nix-installer job exercises the Nix path.
Optional follow-up (separate PR):ca-derivations has been passed since 2023 (Adds code to call nix print-dev-env #456) with no in-repo consumer; on Lix it emits a warning on every command. Worth auditing whether it can be dropped.
Net effect: Devbox works on Lix ≥ 2.95, relies on one fewer experimental feature on upstream Nix, and keeps the binary-cache fast path.
Summary
Lix 2.95 removed
builtins.fetchClosure(see #2946). #2949 added an up-front check that refuses to run on Lix ≥ 2.95 with a clear error. This issue tracks actually restoring compatibility by replacingfetchClosurein the generated flake withbuiltins.appendContext, which needs no experimental feature and works on both Nix and Lix.Background
Why Lix dropped it
From the Lix 2.95 "Kakigōri" release notes:
It's a knock-on effect of Lix removing content-addressed derivations. The input-addressed mode (
inputAddressed = true, the only mode Devbox uses) was considered redundant. No migration path or transition period was provided.Does this affect upstream Nix?
Not currently, and there's no indication it will:
fetch-closureis still an experimental feature in the Nix 2.34 manual; upstream (2.35.x) hasn't removed it.ca-derivations, so Lix's rationale doesn't apply.fetchClosure) is open with maintainers leaning toward stabilization; nobody is proposing removal. Determinate Nix tracks upstream.It is still an experimental feature though, so upstream is free to change it — one more reason to move to a stable primitive.
How Devbox uses
fetchClosureinternal/shellgen/tmpl/flake.nix.tmpl— for every package whose store path is in cache.nixos.org, Devbox pre-builds the path (nix build /nix/store/...), then the generated flake references it via:The template comment already calls this a hack. All we actually need is "a string with store-path context that Nix will
ensurePath", so the flake doesn't have to download/evaluate that package's nixpkgs revision.fetchClosureExprininternal/shellgen/flake_plan.gois dead code:addOutputonly runs forPatchpackages, andIsInBinaryCacheis alwaysfalsefor those. (AndpatchGlibcaccessespkg.name/pkg.buildInputs, so it would break if it were ever reached.)Alternatives considered
builtins.storePath--impureonprint-dev-env, which Devbox deliberately keeps off (featureflag.ImpurePrintDevEnv) because it disables eval caching and leaks env.path:flake inputnixpkgs-<rev>.legacyPackages...(the non-cached path)builtins.appendContextbuiltins.appendContext "/nix/store/x" { "/nix/store/x" = { path = true; }; }yields a string with opaque path context, has no pure-eval restriction, callsstore->ensurePath()(substitutes from configured substituters if missing — same behavior asfetchClosure), needs no experimental feature, and has existed since Nix 2.0.Verification
Built Devbox from
main,devbox add hello@2.12.1, took the generated flake, and swapped thefetchClosureblock for:nix-command flakesonly, pure eval:nix print-dev-env --jsonreturnsbuildInputs = /nix/store/zq74…-hello-2.12.1, hello onPATH. ✅appendContexton acowsaypath not in the local store pulledperl+cowsayfrom cache.nixos.org during eval. ✅nixpkgs-unstable#lixPackageSets.latest.lix), using Devbox's exact flags: the original flake fails withattribute 'fetchClosure' missing; theappendContextflake produces the identical dev env. ✅ (Lix also printswarning: unknown experimental feature 'ca-derivations'/'fetch-closure'— non-fatal.)mkShell'sbuildInputsaccepts plain strings (checkDependencyListallowsstring), which the test confirms.Plan
fetchClosureininternal/shellgen/tmpl/flake.nix.tmplwithbuiltins.appendContext, keeping thebuiltins.trace "downloading …"wrapper and replacing the HACK comment with one explaining the context trick and that the path is pre-built by Devbox (and substituted on demand otherwise). This is the whole fix.fetchClosureExprand theIsInBinaryCachebranch inglibcPatchFlake.addOutput(internal/shellgen/flake_plan.go). Update the stale comments ininternal/shellgen/flake_input.goandinternal/devpkg/narinfo_cache.go.fetch-closurefrom the experimental-features flags ininternal/nix/command.go,internal/nix/nix.go(ExperimentalFlags), and.github/workflows/cli-tests.yaml. Nothing else uses it.internal/nix/install.go: remove theSupportsFetchClosurecheck,LixVersionWithoutFetchClosure, and their tests. KeepInfo.Implementation/IsLix()— the parsing is correct and useful for future diagnostics.appendContextappears,fetchClosuredoesn't), plus manual runs with Nix and Lix 2.95.2 as above. CI's nix-installer job exercises the Nix path.ca-derivationshas been passed since 2023 (Adds code to call nix print-dev-env #456) with no in-repo consumer; on Lix it emits a warning on every command. Worth auditing whether it can be dropped.Net effect: Devbox works on Lix ≥ 2.95, relies on one fewer experimental feature on upstream Nix, and keeps the binary-cache fast path.