Skip to content

Sparse lock files WIP - #579

Open
edolstra wants to merge 26 commits into
mainfrom
lockfile-ng
Open

Sparse lock files WIP#579
edolstra wants to merge 26 commits into
mainfrom
lockfile-ng

Conversation

@edolstra

@edolstra edolstra commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Context

Summary by CodeRabbit

  • New Features

    • Added nix flake diff-locks to compare lock-file changes, including optional transitive dependencies.
    • Added experimental support for lock-file format version 8 while preserving existing formats.
    • Improved flake input resolution, follows handling, and nested dependency locking.
  • Improvements

    • Improved metadata, archive, and input-prefetching support for nested inputs.
    • Formatting tools now support specific hooks and repeated runs until stable.
  • Documentation

    • Added usage documentation and examples for nix flake diff-locks.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e6d4ade-2e9b-4ab4-acb4-75a9d3b6b7bf

📥 Commits

Reviewing files that changed from the base of the PR and between 431b73d and 7c555a9.

📒 Files selected for processing (4)
  • src/libflake/include/nix/flake/settings.hh
  • src/libflake/lockfile-v8.cc
  • src/libutil/experimental-features.cc
  • src/libutil/include/nix/util/experimental-features.hh
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/libflake/include/nix/flake/settings.hh
  • src/libflake/lockfile-v8.cc

📝 Walkthrough

Walkthrough

The flake lock system now supports version 7 and version 8 through an abstract LockedFlake API. Evaluation uses locked-flake primops, consumers use pointer-based ownership, and nix flake diff-locks compares lock states. Formatting commands also support targeted hooks.

Changes

Flake locking architecture

Layer / File(s) Summary
LockedFlake contracts and input paths
src/libflake/include/nix/flake/*, src/libflake/flake-impl.hh, src/libflake/input-attr-path.*
Defines input attribute paths, pointer-based lock results, lock format settings, experimental feature registration, and the abstract LockedFlake interface.
Versioned lock parsing and generation
src/libflake/flake.cc, src/libflake/lockfile-v7.cc, src/libflake/lockfile-v8.cc
Adds version 7 and version 8 lock parsing, serialization, validation, reuse, follows handling, and lock generation.
Evaluator input traversal and fetching
src/libflake/call-flake.nix, src/libflake/flake.cc, src/libflake-c/nix_api_flake.cc
Passes shared locked flakes into evaluation and exposes primops for listing and fetching inputs.
Pointer-based flake consumers
src/libcmd/*, src/nix/develop.cc, src/nix/flake*.cc, src/nix/flake-command.hh
Updates commands and consumers to use pointer-based locked flakes, follows-aware lookup, traversal, and source-path access.
Lock comparison command
src/libflake/diff.cc, src/nix/flake-diff-locks.*, src/nix/meson.build
Adds lock comparison logic, the nix flake diff-locks command, and its documentation.

Formatting workflow

Layer / File(s) Summary
Formatting command updates
CLAUDE.md, maintainers/format.sh
Documents the formatter invocation and adds single-hook and C++-only formatting support.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FlakeCommand
  participant lockFlake
  participant LockedFlakeV7
  participant LockedFlakeV8
  participant callFlake
  participant EvalState
  FlakeCommand->>lockFlake: request locked flake
  lockFlake->>LockedFlakeV7: parse or generate format 7
  lockFlake->>LockedFlakeV8: parse or generate format 8
  lockFlake->>callFlake: evaluate shared locked flake
  callFlake->>EvalState: fetch input source
  EvalState-->>callFlake: return source metadata
  callFlake-->>FlakeCommand: return constructed flake result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding sparse lock file support, while indicating that the work is in progress.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lockfile-ng

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

@github-actions
github-actions Bot temporarily deployed to pull request July 28, 2026 15:11 Inactive
@github-actions
github-actions Bot temporarily deployed to pull request July 28, 2026 18:16 Inactive
@edolstra edolstra added the flake-regression-test Run the flake regressions test suite on this PR label Jul 29, 2026
@github-actions
github-actions Bot temporarily deployed to pull request July 29, 2026 14:42 Inactive
@github-actions
github-actions Bot temporarily deployed to pull request July 29, 2026 15:06 Inactive
@github-actions
github-actions Bot temporarily deployed to pull request July 29, 2026 17:38 Inactive
@github-actions
github-actions Bot temporarily deployed to pull request August 3, 2026 14:40 Inactive
Base automatically changed from sync-2.35 to main August 3, 2026 17:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
src/libflake/include/nix/flake/settings.hh (1)

45-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document that version 8 is incomplete.

LockedFlakeV8 throws for getInputTargets, findInput, and getSourcePath. A user who sets lock-file-format = 8 can therefore create a lock file that cannot be evaluated. Add a note to the setting description while version 8 support is in progress.

📝 Proposed documentation change
         R"(
           The lock file format version to use when creating a new lock
           file (7 or 8). An existing lock file keeps its version
           unless `--recreate-lock-file` is passed.
+
+          Version 8 is experimental and incomplete: evaluation of
+          version 8 lock files is not supported yet.
         )",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libflake/include/nix/flake/settings.hh` around lines 45 - 55, Update the
lock-file-format setting description in Setting<unsigned int> to note that
version 8 support is incomplete and may produce lock files that cannot be
evaluated because LockedFlakeV8 lacks required operations; preserve the existing
version and recreate behavior documentation.
src/libflake/lockfile-v7.cc (1)

700-745: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify the reuse condition for the "update all" case.

lockFlags.inputUpdates == std::nullopt now means "update all inputs". At Line 707 the guard !(lockFlags.inputUpdates && ...->count(...)) evaluates to true in that case, which reads as "reuse the old lock". The behavior is still correct only because Line 887 passes nullptr as oldNode when inputUpdates is unset. Add a comment or test the flag directly, so a later change to Line 887 does not silently re-enable reuse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libflake/lockfile-v7.cc` around lines 700 - 745, Clarify the old-lock
lookup guard around oldNode and get(oldNode->inputs, id) so
lockFlags.inputUpdates == std::nullopt explicitly represents update-all and
cannot permit reuse. Test the flag directly or add a focused comment documenting
that reuse is allowed only when inputUpdates is present and the current input is
not selected for update, preserving the existing behavior.
src/libflake/call-flake.nix (1)

87-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the bare assertions with explicit errors.

assert builtins.isFunction flake.outputs; and assert !info.buildTime; produce assertion messages that do not name the input. A build-time input that is also marked as a flake therefore fails with an opaque message. Use throw with the input attribute path to help users diagnose the cause.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libflake/call-flake.nix` around lines 87 - 93, In the flake branch of the
result construction, replace the bare assertions on flake.outputs and
info.buildTime with explicit throw errors that include the input attribute path.
Preserve the existing validation conditions and result behavior for valid
inputs, while ensuring both invalid cases identify the offending input.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Line 3: Update the formatter command guidance in CLAUDE.md to use executable
commands through the Nix development shell: document `nix develop -c
./maintainers/format.sh` generally and `nix develop -c ./maintainers/format.sh
clang-format` for C++-only changes, removing the extra `run` token.

In `@maintainers/format.sh`:
- Line 19: Update the retry loop around the pre-commit invocation in the format
script to record the worktree state before each run and retry only if the failed
run modifies files. When the run fails without changing the worktree, exit with
its failure status instead of looping indefinitely; preserve the existing
until-stable behavior for failures that do change files.

In `@src/libflake/call-flake.nix`:
- Around line 56-59: Update callFlake and the lock-file parsing flow around
parseLockFileV7 to validate follows relationships for cycles before evaluating
the LockedFlake. Apply the same validation to version 8 follows entries when
getInputTargets is implemented, and reject cyclic inputs before listFlakeInputs
or callFlake processes them.

In `@src/libflake/flake.cc`:
- Around line 911-928: Update LockedFlake::visit to track visited version 7 node
identities while traversing inputs, and skip callbacks and recursion for nodes
already visited. Deduplicate by the lock-file node identity rather than
lockedRef, preserving traversal of distinct nodes that share a reference while
preventing repeated processing of shared nodes across input paths.

In `@src/libflake/lockfile-v7.cc`:
- Around line 122-145: Validate parentInputAttrPath after the lockfile has been
fully parsed, ensuring every relative input has a parent and that following each
parent chain terminates without revisiting a node; reject missing,
self-referential, or cyclic parent references with a Nix Error before
getSourcePath or prim_fetchFlakeInput can recurse.

In `@src/libflake/lockfile-v8.cc`:
- Around line 257-296: Prevent users from selecting unsupported lock-file format
8 until LockedFlakeV8 supports evaluation: update the lockFileFormat setting
validation to reject version 8, and update
src/libflake/include/nix/flake/settings.hh lines 45-55 to state that version 8
is experimental and its evaluation is not yet supported. No direct change is
required in src/libflake/lockfile-v8.cc lines 257-296; its existing
notImplemented handlers document the unsupported operations.
- Around line 342-352: Update the version 8 override handling around the
input-override loop and its recursion in lockFlake so each path component is
validated against declared inputs before creating or traversing an override
entry. For nonexistent transitive inputs, skip locking and do not add the path
to overridesUsed, allowing the existing unmatched-override warning to be
emitted; only record and process overrides whose full path matches declared
inputs.

In `@src/nix/flake.cc`:
- Around line 268-276: Restore cycle protection for lock-graph traversal used by
LockedFlake::visit, ensuring cyclic version 7 graphs cannot recurse indefinitely
or create unbounded TreeNode instances. Confirm that lock parsing rejects cycles
before visit, or add a bounded depth/visited-node guard within visit itself so
both metadata and archive traversals remain protected.
- Around line 281-303: Guard access to child.second.input in the tree-printing
lambda before dereferencing it with std::get_if. Skip nodes whose optional input
is disengaged, while preserving the existing LockedFlake::InputInfo and
InputAttrPath printing behavior for populated entries.

---

Nitpick comments:
In `@src/libflake/call-flake.nix`:
- Around line 87-93: In the flake branch of the result construction, replace the
bare assertions on flake.outputs and info.buildTime with explicit throw errors
that include the input attribute path. Preserve the existing validation
conditions and result behavior for valid inputs, while ensuring both invalid
cases identify the offending input.

In `@src/libflake/include/nix/flake/settings.hh`:
- Around line 45-55: Update the lock-file-format setting description in
Setting<unsigned int> to note that version 8 support is incomplete and may
produce lock files that cannot be evaluated because LockedFlakeV8 lacks required
operations; preserve the existing version and recreate behavior documentation.

In `@src/libflake/lockfile-v7.cc`:
- Around line 700-745: Clarify the old-lock lookup guard around oldNode and
get(oldNode->inputs, id) so lockFlags.inputUpdates == std::nullopt explicitly
represents update-all and cannot permit reuse. Test the flag directly or add a
focused comment documenting that reuse is allowed only when inputUpdates is
present and the current input is not selected for update, preserving the
existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62667b9a-510e-4b83-9121-7ddd8e3806f1

📥 Commits

Reviewing files that changed from the base of the PR and between 377a73b and b8bdca3.

📒 Files selected for processing (29)
  • CLAUDE.md
  • maintainers/format.sh
  • src/libcmd/flake-schemas.cc
  • src/libcmd/include/nix/cmd/command.hh
  • src/libcmd/installable-flake.cc
  • src/libcmd/installables.cc
  • src/libcmd/repl.cc
  • src/libflake-c/nix_api_flake.cc
  • src/libflake/call-flake.nix
  • src/libflake/diff.cc
  • src/libflake/flake-impl.hh
  • src/libflake/flake.cc
  • src/libflake/include/nix/flake/flake.hh
  • src/libflake/include/nix/flake/input-attr-path.hh
  • src/libflake/include/nix/flake/lockfile.hh
  • src/libflake/include/nix/flake/meson.build
  • src/libflake/include/nix/flake/settings.hh
  • src/libflake/input-attr-path.cc
  • src/libflake/lockfile-v7.cc
  • src/libflake/lockfile-v8.cc
  • src/libflake/lockfile.cc
  • src/libflake/meson.build
  • src/nix/develop.cc
  • src/nix/flake-command.hh
  • src/nix/flake-diff-locks.cc
  • src/nix/flake-diff-locks.md
  • src/nix/flake-prefetch-inputs.cc
  • src/nix/flake.cc
  • src/nix/meson.build
💤 Files with no reviewable changes (4)
  • src/libcmd/repl.cc
  • src/libcmd/include/nix/cmd/command.hh
  • src/libflake/include/nix/flake/lockfile.hh
  • src/libflake/lockfile.cc

Comment thread CLAUDE.md
Observe the following rules when contributing to this repository:

* Before committing, run ./maintainers/format.sh to detect/fix any formatting issues.
* Before committing, run `./maintainers/format.sh` to detect/fix any formatting issues. If you've only touched C++ files, run `run ./maintainers/format.sh clang-format` since it's a lot faster.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- CLAUDE.md ---'
cat -n CLAUDE.md | sed -n '1,12p'

printf '%s\n' '--- maintainer files ---'
fd -t f 'format\.sh$|flake\.nix$|shell\.nix$|pre-commit' . | sort

printf '%s\n' '--- format.sh ---'
format_script="$(fd -t f '^format\.sh$' . | head -n 1)"
cat -n "$format_script"

printf '%s\n' '--- related references ---'
rg -n --hidden -g '!node_modules' '_NIX_PRE_COMMIT_HOOKS_CONFIG|nix develop|format\.sh|clang-format' .

Repository: DeterminateSystems/nix-src

Length of output: 13601


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- CI invocation ---'
cat -n ci/gha/tests/pre-commit-checks | sed -n '1,28p'

printf '%s\n' '--- development-shell environment ---'
cat -n packaging/dev-shell.nix | sed -n '250,275p'

printf '%s\n' '--- nix develop command documentation ---'
cat -n src/nix/develop.md | sed -n '68,82p'

printf '%s\n' '--- command-token verifier ---'
python3 - <<'PY'
import shlex

commands = [
    "./maintainers/format.sh",
    "run ./maintainers/format.sh clang-format",
    "nix develop -c ./maintainers/format.sh",
    "nix develop -c ./maintainers/format.sh clang-format",
]

for command in commands:
    print(f"{command!r} -> {shlex.split(command)!r}")
PY

Repository: DeterminateSystems/nix-src

Length of output: 2831


Use executable formatter commands.

From a normal shell, document nix develop -c ./maintainers/format.sh and nix develop -c ./maintainers/format.sh clang-format. Remove the extra run token.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 3, Update the formatter command guidance in CLAUDE.md to
use executable commands through the Nix development shell: document `nix develop
-c ./maintainers/format.sh` generally and `nix develop -c
./maintainers/format.sh clang-format` for C++-only changes, removing the extra
`run` token.

Source: Learnings

Comment thread maintainers/format.sh
hook="${1:-}"
fi

while ! pre-commit run --config "$_NIX_PRE_COMMIT_HOOKS_CONFIG" --all-files ${hook:+"$hook"}; do

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- maintainers/format.sh ---'
cat -n maintainers/format.sh
printf '%s\n' '--- related references ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'until-stable|pre-commit run|format\.sh' .

Repository: DeterminateSystems/nix-src

Length of output: 2403


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
p = Path("maintainers/format.sh")
text = p.read_text()
print("has_until_stable=", "--until-stable" in text)
for i, line in enumerate(text.splitlines(), 1):
    if 15 <= i <= 25:
        print(f"{i}: {line}")
PY

Repository: DeterminateSystems/nix-src

Length of output: 457


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
lines = Path("maintainers/format.sh").read_text().splitlines()
for n, line in enumerate(lines, 1):
    if 1 <= n <= 40:
        print(f"{n}: {line}")
PY

Repository: DeterminateSystems/nix-src

Length of output: 923


Stop retrying persistent pre-commit failures forever.

With --until-stable, every non-zero pre-commit result triggers another run. An unchanged worktree or configuration error causes an infinite loop. Retry only when the failed run changes files; otherwise exit with the failure status.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@maintainers/format.sh` at line 19, Update the retry loop around the
pre-commit invocation in the format script to record the worktree state before
each run and retry only if the failed run modifies files. When the run fails
without changing the worktree, exit with its failure status instead of looping
indefinitely; preserve the existing until-stable behavior for failures that do
change files.

Comment thread src/libflake/call-flake.nix
Comment thread src/libflake/flake.cc
Comment on lines +122 to +145
LockedNode(const fetchers::Settings & fetchSettings, const nlohmann::json & json)
: lockedRef(getFlakeRef(fetchSettings, json, "locked", "info")) // FIXME: remove "info"
, originalRef(getFlakeRef(fetchSettings, json, "original", nullptr))
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
, buildTime(json.find("buildTime") != json.end() ? (bool) json["buildTime"] : false)
, parentInputAttrPath(
json.find("parent") != json.end() ? (std::optional<InputAttrPath>) json["parent"] : std::nullopt)
{
if (!lockedRef.input.isLocked(fetchSettings) && !lockedRef.input.isRelative()) {
if (lockedRef.input.getNarHash())
warn(
"Lock file entry '%s' is unlocked (e.g. lacks a Git revision) but is checked by NAR hash. "
"This is not reproducible and will break after garbage collection or when shared.",
lockedRef.to_string());
else
throw Error(
"Lock file contains unlocked input '%s'. Use '--allow-dirty-locks' to accept this lock file.",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}

// For backward compatibility, lock file entries are implicitly final.
assert(!lockedRef.input.attrs.contains("__final"));
lockedRef.input.attrs.insert_or_assign("__final", Explicit<bool>(true));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate the parent field when parsing a node.

parentInputAttrPath is taken from the lock file without any check. Two downstream paths trust it:

  • LockedFlakeV7::getSourcePath (Line 445) calls lockedNode->parentInputAttrPath.value() for a relative input. A lock file that omits parent for a relative input causes std::bad_optional_access, which is not reported as a Nix error.
  • The same call recurses into the parent path. prim_fetchFlakeInput in src/libflake/flake.cc (Lines 800-807) loops over the same field. A lock file whose parent points at the node itself, or at another relative input that points back, causes unbounded recursion or an infinite loop.

A flake.lock comes from a fetched flake, so it is untrusted input. Reject a missing or self-referential parent during parsing.

🛡️ Suggested validation direction
         // For backward compatibility, lock file entries are implicitly final.
         assert(!lockedRef.input.attrs.contains("__final"));
         lockedRef.input.attrs.insert_or_assign("__final", Explicit<bool>(true));
+
+        if (lockedRef.input.isRelative() && !parentInputAttrPath)
+            throw Error(
+                "lock file entry for relative input '%s' lacks a 'parent' attribute", lockedRef.to_string());

Add a separate check after parsing the whole file that the parent chain of every relative input terminates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libflake/lockfile-v7.cc` around lines 122 - 145, Validate
parentInputAttrPath after the lockfile has been fully parsed, ensuring every
relative input has a parent and that following each parent chain terminates
without revisiting a node; reject missing, self-referential, or cyclic parent
references with a Nix Error before getSourcePath or prim_fetchFlakeInput can
recurse.

Comment thread src/libflake/lockfile-v8.cc Outdated
Comment on lines +342 to +352
for (auto & [path, ref] : lockFlags.inputOverrides) {
auto input = get(flake.inputs, path.get().front());
if (!input)
continue;
for (auto & elem : std::views::drop(path.get(), 1))
input = &input->overrides[elem];
input->ref = ref;
input->follows = std::nullopt;
overridesUsed.insert(path);
explicitCliOverrides.insert(path);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Version 8 does not validate overrides against declared inputs.

Two problems follow from this block and from the override recursion at Lines 540-558:

  • input = &input->overrides[elem] creates the override entry for every path element after the first. Therefore overridesUsed.insert(path) marks the override as used even when the flake declares no such transitive input. The "does not match any input" warning in lockFlake() is then never printed for those paths.
  • createLock is later called for that override (Line 554), so Nix fetches the flakeref and writes a lock entry for an input that no flake.nix declares. Version 7 instead warns about an override for a non-existent input and never locks it.

Validate each override path against the declared inputs of the containing flake before locking it, and only insert into overridesUsed when the path matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libflake/lockfile-v8.cc` around lines 342 - 352, Update the version 8
override handling around the input-override loop and its recursion in lockFlake
so each path component is validated against declared inputs before creating or
traversing an override entry. For nonexistent transitive inputs, skip locking
and do not add the path to overridesUsed, allowing the existing
unmatched-override warning to be emitted; only record and process overrides
whose full path matches declared inputs.

Comment thread src/nix/flake.cc
Comment thread src/nix/flake.cc
Comment on lines +281 to +303
[&](this const auto & recurse, const TreeNode & node, const std::string & prefix) -> void {
for (const auto & [last, child] : markLast(node.children)) {
if (auto inputInfo = std::get_if<flake::LockedFlake::InputInfo>(&*child.second.input)) {
std::string lastModifiedStr = "";
if (auto lastModified = (*lockedNode)->lockedRef.input.getLastModified())
if (auto lastModified = inputInfo->lockedRef.input.getLastModified())
lastModifiedStr = fmt(" (%s)", std::put_time(std::gmtime(&*lastModified), "%F %T"));
logger->cout(
"%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s%s",
prefix + (last ? treeLast : treeConn),
input.first,
(*lockedNode)->lockedRef.to_string(true),
child.first,
inputInfo->lockedRef.to_string(true),
lastModifiedStr);

bool firstVisit = visited.insert(*lockedNode).second;

if (firstVisit)
recurse(**lockedNode, prefix + (last ? treeNull : treeLine));
} else if (auto follows = std::get_if<1>(&input.second)) {
recurse(child.second, prefix + (last ? treeNull : treeLine));
} else if (auto follows = std::get_if<flake::InputAttrPath>(&*child.second.input)) {
logger->cout(
"%s" ANSI_BOLD "%s" ANSI_NORMAL " follows input '%s'",
prefix + (last ? treeLast : treeConn),
input.first,
child.first,
flake::printInputAttrPath(*follows));
}
}
}(*lockedFlake.lockFile.root, "");
}(root, "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the optional dereference before printing a node.

TreeNode::input stays disengaged for any node that the traversal creates as an intermediate but never reports through the callback. &*child.second.input then dereferences a disengaged std::optional, which is undefined behavior. The current visit order reports a parent before its children, so this path is not reachable today. The version 8 sparse model nests inputs that have no lock file of their own, so keep the print loop robust to a missing entry.

🛡️ Proposed guard
             [&](this const auto & recurse, const TreeNode & node, const std::string & prefix) -> void {
                 for (const auto & [last, child] : markLast(node.children)) {
-                    if (auto inputInfo = std::get_if<flake::LockedFlake::InputInfo>(&*child.second.input)) {
+                    if (!child.second.input) {
+                        recurse(child.second, prefix + (last ? treeNull : treeLine));
+                        continue;
+                    }
+                    if (auto inputInfo = std::get_if<flake::LockedFlake::InputInfo>(&*child.second.input)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[&](this const auto & recurse, const TreeNode & node, const std::string & prefix) -> void {
for (const auto & [last, child] : markLast(node.children)) {
if (auto inputInfo = std::get_if<flake::LockedFlake::InputInfo>(&*child.second.input)) {
std::string lastModifiedStr = "";
if (auto lastModified = (*lockedNode)->lockedRef.input.getLastModified())
if (auto lastModified = inputInfo->lockedRef.input.getLastModified())
lastModifiedStr = fmt(" (%s)", std::put_time(std::gmtime(&*lastModified), "%F %T"));
logger->cout(
"%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s%s",
prefix + (last ? treeLast : treeConn),
input.first,
(*lockedNode)->lockedRef.to_string(true),
child.first,
inputInfo->lockedRef.to_string(true),
lastModifiedStr);
bool firstVisit = visited.insert(*lockedNode).second;
if (firstVisit)
recurse(**lockedNode, prefix + (last ? treeNull : treeLine));
} else if (auto follows = std::get_if<1>(&input.second)) {
recurse(child.second, prefix + (last ? treeNull : treeLine));
} else if (auto follows = std::get_if<flake::InputAttrPath>(&*child.second.input)) {
logger->cout(
"%s" ANSI_BOLD "%s" ANSI_NORMAL " follows input '%s'",
prefix + (last ? treeLast : treeConn),
input.first,
child.first,
flake::printInputAttrPath(*follows));
}
}
}(*lockedFlake.lockFile.root, "");
}(root, "");
[&](this const auto & recurse, const TreeNode & node, const std::string & prefix) -> void {
for (const auto & [last, child] : markLast(node.children)) {
if (!child.second.input) {
recurse(child.second, prefix + (last ? treeNull : treeLine));
continue;
}
if (auto inputInfo = std::get_if<flake::LockedFlake::InputInfo>(&*child.second.input)) {
std::string lastModifiedStr = "";
if (auto lastModified = inputInfo->lockedRef.input.getLastModified())
lastModifiedStr = fmt(" (%s)", std::put_time(std::gmtime(&*lastModified), "%F %T"));
logger->cout(
"%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s%s",
prefix + (last ? treeLast : treeConn),
child.first,
inputInfo->lockedRef.to_string(true),
lastModifiedStr);
recurse(child.second, prefix + (last ? treeNull : treeLine));
} else if (auto follows = std::get_if<flake::InputAttrPath>(&*child.second.input)) {
logger->cout(
"%s" ANSI_BOLD "%s" ANSI_NORMAL " follows input '%s'",
prefix + (last ? treeLast : treeConn),
child.first,
flake::printInputAttrPath(*follows));
}
}
}(root, "");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/nix/flake.cc` around lines 281 - 303, Guard access to child.second.input
in the tree-printing lambda before dereferencing it with std::get_if. Skip nodes
whose optional input is disengaged, while preserving the existing
LockedFlake::InputInfo and InputAttrPath printing behavior for populated
entries.

@github-actions
github-actions Bot temporarily deployed to pull request August 3, 2026 18:59 Inactive
edolstra added 13 commits August 5, 2026 19:37
In preparation for the version 8 sparse lock file format, rename
LockFile to LockFileV7 and move it, together with the Node and
LockedNode data types, to a new header lockfile-v7.hh (implementation
in lockfile-v7.cc). lockfile.hh continues to contain the common types
like InputAttrPath and NonEmptyInputAttrPath.

No behaviour change.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
In preparation for the version 8 sparse lock file format, turn
`LockedFlake` into an abstract base class that doesn't expose the
version 7 lock file representation, and move that representation into
a new subclass `LockedFlakeV7`. The `lockFlake()` functions now return
a `std::unique_ptr<LockedFlake>`.

Code that needs to query a locked flake now goes through virtual
methods instead of inspecting the version 7 node graph directly:

* `isUnlocked()`: check for unlocked or non-final inputs. This allows
  `getFingerprint()` to be implemented entirely in terms of the
  abstract interface.

* `toJSON()`, with non-virtual `to_string()` and `operator <<` on top
  of it.

* `getInputNames()`: return the names of the inputs of the input
  denoted by an attribute path prefix.

* `findInput()`: look up an input by attribute path, resolving
  "follows" indirections, and return an `InputInfo` struct containing
  its locked reference. Used by `--inputs-from` and
  `InstallableFlake::nixpkgsFlakeRef()`.

* `visit()`: walk all transitive inputs in depth-first order, calling
  a callback with either an `InputInfo` or the target path of a
  "follows" input. The callback controls recursion, e.g. to skip
  build-time inputs. Used by `nix flake metadata`, `nix flake archive`
  and `nix flake prefetch-inputs`.

The only remaining uses of `LockedFlakeV7` are in `lockFlake()` and
`callFlake()` in libflake itself, which will become the dispatch
points between the version 7 and version 8 implementations.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
The `computeLocks` machinery is moved from the free function
`lockFlake()` in flake.cc into a new static member function
`LockedFlakeV7::lockFlake()` in lockfile-v7.cc. The free function now
only contains version-independent work, expressed in terms of the
abstract `LockedFlake` interface:

* Reading the old lock file into a `nlohmann::json`, and constructing
  a `LockedFlake` from it (via a new `LockedFlakeV7` constructor that
  takes the JSON). This is where we will dispatch on the lock file
  version in the future.

* Change detection, by comparing the virtual `toJSON()` serializations
  of the old and new locked flakes. This also does the right thing
  across future version migrations (a version change is always a
  change).

* Printing the diff, via a new abstract `LockedFlake::diff()` method
  that shows the differences relative to an older `LockedFlake`. The
  version 7 implementation diffs against an empty lock file if the old
  lock file is not version 7, so all inputs show up as added.

* Writing/committing the new lock file, using `to_string()` and the
  virtual `isUnlocked()`.

`LockedFlakeV7::lockFlake()` takes the old lock file as a `const
LockedFlake &` and downcasts it internally. The `LockFileV7` parsing
constructor now takes a `nlohmann::json` instead of the file contents,
and `getFlake()` with a lock root attribute path is exported from
flake.cc since the lock algorithm needs it from its new home.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
This abstract method returns the source path of the input denoted by
an input attribute path (or of the top-level flake if the path is
empty), fetching the input if necessary. The returned path is backed
by `EvalState::rootFS`, i.e. it's a store path - possibly a virtual
one with the input's accessor mounted on it if lazy trees are
enabled. `nix flake prefetch-inputs` now uses this method instead of
accessing the version 7 node graph directly, so when lazy trees are
disabled, inputs are copied to the store via `EvalState::mountInput()`
(which also verifies their NAR hashes).

In `LockedFlakeV7`, the `nodePaths` map is replaced by a `mutable
Sync<std::optional<SourcePath>>` field in `LockedNode`, i.e. the
source path of a fetched node is now stored in the node itself. It is
set by `LockedFlakeV7::lockFlake()` for nodes fetched during locking,
and cached by `getSourcePath()` for nodes fetched on demand (where
relative path inputs are resolved against the source path of their
parent flake, and the `subdir` of the locked flakeref is taken into
account). `callFlake()` reconstructs the node -> source path map by
walking the node graph.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Instead of the version 7 lock file JSON and an attrset of pre-fetched
source trees ("overrides"), call-flake.nix now receives an external
value wrapping the C++ `LockedFlake` object, along with two internal
primops (registered with `internal = true`, so they're not exposed to
the user):

* `listFlakeInputs lockedFlake inputAttrPath`: returns the inputs of
  the input denoted by `inputAttrPath` as an attrset mapping input
  names to either null (for a regular input) or the input attribute
  path of the target of a "follows" input. This only consults the
  lock data, so it doesn't fetch anything.

* `fetchFlakeInput lockedFlake inputAttrPath`: fetches an input (via
  `LockedFlake::getSourcePath()`) and returns its `sourceInfo`
  attributes and flake subdirectory; or, for build-time inputs, the
  locked input attributes (without fetching).

As a result, fetching, override handling, relative path handling and
the lock file node graph all disappear from call-flake.nix - it just
lazily constructs a tree of inputs keyed by input attribute path,
resolving "follows" by walking the edges of that tree from the
top-level flake. This preserves evaluation sharing (every distinct
input is constructed only once) and prepares for the version 8 lock
file format, whose `LockedFlake` implementation will only need to
provide the same abstract interface.

Also:

* `callFlake()` now takes a `std::shared_ptr<const LockedFlake>`
  since the thunks created by call-flake.nix reference the
  `LockedFlake` object, which therefore must be kept alive for the
  lifetime of the evaluator.

* `LockedFlake::getInputTargets()` is a new abstract method backing
  `listFlakeInputs`; `getInputNames()` is now a non-virtual wrapper
  around it.

* `LockedFlakeV7::findInput()` now returns the `buildTime` and
  `parentInputAttrPath` fields (the former was dropped before, but
  wasn't used).

* The `KeyMap` returned by `LockFileV7::toJSON()` is no longer
  needed, since lock file node keys were only used by callFlake().

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
These types are no longer used outside of lockfile-v7.cc, so they can
be implementation details. The header now only exposes two free
functions returning `std::unique_ptr<LockedFlake>`:

* `parseLockFileV7()`: construct a `LockedFlake` from the JSON
  contents of a version 5-7 lock file.

* `lockFlakeV7()` (previously `LockedFlakeV7::lockFlake()`): compute a
  new lock file.

This also keeps the version dispatch in the free function `lockFlake()`
symmetrical for the future version 8 implementation: it will just pick
between `parseLockFileV{7,8}()` and `lockFlakeV{7,8}()` based on the
`version` field in the lock file JSON.

Also remove the unused `LockedNode::computeStorePath()`.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Now that these classes are local to this file, there is no need for
separate declarations and definitions.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
It's the input attribute path, relative to the top-level flake, of
the flake that *declares* the relative path (i.e. the flake that
declares the override, in the case of overridden inputs), which is
not necessarily the input's parent in the lock file graph.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
`std::nullopt` now means "update all inputs". Bare `nix flake update`
uses this instead of setting `recreateLockFile`, so recreating the lock
file is now only triggered by the explicit `--recreate-lock-file` flag
(which is hereby un-deprecated). This distinction matters for the
upcoming lock file version dispatch: `nix flake update` will keep the
lock file's existing format, while `--recreate-lock-file` will switch
it to the configured format.

Note that "update all inputs" is implemented by ignoring the
*top-level* old lock file (as `recreateLockFile` did), not by refusing
reuse at every path: entries seeded from a dependency's own lock file
(e.g. a vendored subflake) must still be copied from it.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Selects the lock file format version (currently 7, the default, or 8)
used when creating a new lock file. An existing lock file keeps its
version unless `--recreate-lock-file` is passed. Settable via nix.conf,
`--option lock-file-format N` or the auto-generated `--lock-file-format`
flag.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
lockFlakeV7() now returns a LockFlakeResult struct containing the
locked flake and the overridesUsed/updatesUsed sets, so the warnings
about unmatched '--override-input' / update flags can be emitted by
lockFlake(). This allows the upcoming lockFlakeV8() to share that
code.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
edolstra added 12 commits August 5, 2026 19:37
…ockedFlakes()

The new free function diffLockedFlakes() (in diff.cc) can diff two
LockedFlakes of any version. It relies on two new LockedFlake virtual
methods: version(), used to report lock file version changes (which
previously happened in lockFlake()), and getAllLockEntries(), which
flattens the contents of a lock file into a map from input attribute
paths to locked flakerefs or 'follows' targets.

A nice side effect is that migrating an unchanged lock file between
versions 7 and 8 now shows just the version change, rather than every
input as "added".

getAllLockEntries() takes a `fetchTransitive` flag (currently
unimplemented for version 8) that will allow a future `nix flake
diff-locks` command to include the transitive locks of inputs that
have a lock file of their own.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Shows the differences between the lock files of two flakes, e.g.

  # nix flake diff-locks github:NixOS/nix/2.28.0 github:NixOS/nix/2.29.0
  • Updated input 'nixpkgs':
      'github:NixOS/nixpkgs/48d12d5' (2024-12-16)
    → 'github:NixOS/nixpkgs/adaa24f' (2025-05-13)

The second argument defaults to the flake in the current directory.
The flakes do not need to use the same lock file format version. The
--transitive flag (not yet supported for version 8 lock files)
includes the transitive locks of inputs that have a lock file of their
own.

Also factors the version dispatch in lockFlake() into a new generic
parseLockFile() function that the command uses to read lock files of
any version.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
This function will be identitical between lockFlakeV7() and
lockFlakeV8(), so move it into a utility function in the new internal
header flake-impl.hh (it's not part of the public libflake API).

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
It can be implemented generically on top of getInputTargets() (which
provides the input names and 'follows' targets of each level) and
findInput() (which provides the InputInfo of non-follows inputs), so
the per-version implementations are unnecessary. Note that we now
only recurse into inputs with `isFlake = true`; in a well-formed
version 7 lock file, non-flake nodes never have children anyway.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
getInputTargets(), findInput() and getSourcePath() now require an
input attribute path that doesn't pass through any 'follows' input,
and throw an error otherwise. The new non-virtual resolveFollows()
method resolves a path into that form, implemented generically on top
of getInputTargets() (which returns the immediate 'follows' targets).

This simplifies the per-version implementations: LockFileV7's node
lookup no longer resolves 'follows' edges (and the check that
'follows' targets exist, previously the other user of that
resolution, now uses resolveFollows() in lockFlakeV7()). The only
external callers that need resolveFollows() are '--inputs-from' and
InstallableFlake::nixpkgsFlakeRef(), where the named input may be a
'follows'; call-flake.nix and visit() only ever recurse into resolved
paths.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
getInputTargets(), findInput(), visit() and the non-virtual
getInputNames() now take an EvalState, like getSourcePath() already
did. The version 7 implementations don't need it (their lock files
contain the full dependency graph), but the version 8 implementations
will have to fetch inputs and read their flake.nix / flake.lock files
on demand.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
GCC segfaults while parsing the recursive lambda with an explicit
object parameter ("deducing this") when it contains a call to a
member function of the enclosing class. Use the std::function
recursion pattern instead.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Add lockfile-v8.cc containing the in-memory representation of the new
sparse lock file format: a flat map from slash-separated input
attribute paths to locks (with recursive inline `locks` for inputs
that lack a lock file of their own), along with JSON parsing,
serialization, isUnlocked() and diff(). The eval-time methods
(getInputTargets() etc.) and the lock algorithm are stubs for now.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
lockFlakeV8() locks the immediate inputs of the flake, plus any
overrides of transitive inputs declared in its flake.nix (stored as
slash-separated paths like "foo/nixpkgs"), reusing entries from the
old lock file when the flakeref is unchanged. 'follows' and relative
path inputs are not stored; the latter are required to have a lock
file of their own. Inputs that don't have a lock file get their
transitive inputs locked inline in a recursive `locks` attribute,
which is pruned when the input gains a lock file on a later update.

Command line overrides are applied by merging them into the top-level
flake's input tree, so they're handled uniformly with overrides
declared in flake.nix (with the outermost override winning).

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
The old lock file is parsed with the parser matching its version. The
version of the new lock file is that of the existing lock file; only
`--recreate-lock-file` (or the absence of a lock file) switches to the
version configured by the `lock-file-format` setting. Version changes
are shown in the lock file diff.

lockFlakeV7() now ignores a non-v7 old lock file instead of throwing
`std::bad_cast`, so migrating from version 8 back to 7 works.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Say "takes precedence over" instead of "shadows", and refer to the
entries in the recursive `locks` attribute as "nested" entries rather
than "inline" ones.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
LockedFlakeV8 now implements getInputTargets(), findInput() and
getSourcePath(), making `nix build`/`nix eval` etc. work on version 8
lock files.

Since a sparse lock file only contains the locks of the immediate
inputs of a flake (plus overrides and the nested `locks` of inputs
that don't have a lock file of their own), the resolution of
transitive inputs is *delegated*: for each input, a child
`LockedFlake` is constructed lazily by fetching the input and reading
its own lock file (which may use any lock file version - so the
entire dependency graph of a version 7 lock file is honored for its
subtree), or from the nested `locks` of its entry. Queries below an
input are forwarded to its child, translating input attribute paths
and 'follows' targets between the child's namespace and ours.

Override entries in our lock file (slash-separated keys like
"foo/nixpkgs") take precedence over the delegated resolution, and
relative path inputs are resolved against our own source tree and
delegated via their own lock file.

Per the LockedFlake contract, query paths must be fully resolved: an
error is thrown when the descent encounters an input declared as a
'follows'. Note that unlike version 7, which stores 'follows' in the
lock file, we have to check the flake.nix declarations for this -
without it, a path crossing one of our 'follows' overrides would be
delegated to a child that doesn't know about the override.

Note that a missing entry in a transitive lock file is a fatal
evaluation error: unlike version 7, version 8 does not silently
repair incomplete transitive lock files. (The top-level lock file is
still completed by lockFlake() before evaluation, as before.)

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/libflake/call-flake.nix (1)

96-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a clear error for a dangling follows target.

getInputByAttrPath indexes input.edges.${name} without a membership check. If a lock file names a follows target that does not exist, evaluation fails with a generic missing-attribute error. Lock parsing is expected to reject such targets, so this only affects the error message quality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libflake/call-flake.nix` around lines 96 - 103, Update getInputByAttrPath
to validate that each name exists in input.edges before indexing it, and raise a
clear error identifying the dangling follows target when it is absent. Preserve
the existing traversal and final-input behavior for valid attribute paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/libflake/call-flake.nix`:
- Around line 96-103: Update getInputByAttrPath to validate that each name
exists in input.edges before indexing it, and raise a clear error identifying
the dangling follows target when it is absent. Preserve the existing traversal
and final-input behavior for valid attribute paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d7872156-fc45-42b7-a53b-07774c2dbec2

📥 Commits

Reviewing files that changed from the base of the PR and between 3fb5d21 and 431b73d.

📒 Files selected for processing (29)
  • CLAUDE.md
  • maintainers/format.sh
  • src/libcmd/flake-schemas.cc
  • src/libcmd/include/nix/cmd/command.hh
  • src/libcmd/installable-flake.cc
  • src/libcmd/installables.cc
  • src/libcmd/repl.cc
  • src/libflake-c/nix_api_flake.cc
  • src/libflake/call-flake.nix
  • src/libflake/diff.cc
  • src/libflake/flake-impl.hh
  • src/libflake/flake.cc
  • src/libflake/include/nix/flake/flake.hh
  • src/libflake/include/nix/flake/input-attr-path.hh
  • src/libflake/include/nix/flake/lockfile.hh
  • src/libflake/include/nix/flake/meson.build
  • src/libflake/include/nix/flake/settings.hh
  • src/libflake/input-attr-path.cc
  • src/libflake/lockfile-v7.cc
  • src/libflake/lockfile-v8.cc
  • src/libflake/lockfile.cc
  • src/libflake/meson.build
  • src/nix/develop.cc
  • src/nix/flake-command.hh
  • src/nix/flake-diff-locks.cc
  • src/nix/flake-diff-locks.md
  • src/nix/flake-prefetch-inputs.cc
  • src/nix/flake.cc
  • src/nix/meson.build
💤 Files with no reviewable changes (4)
  • src/libflake/lockfile.cc
  • src/libcmd/include/nix/cmd/command.hh
  • src/libcmd/repl.cc
  • src/libflake/include/nix/flake/lockfile.hh
🚧 Files skipped from review as they are similar to previous changes (22)
  • src/libflake-c/nix_api_flake.cc
  • src/libcmd/installables.cc
  • src/libflake/include/nix/flake/input-attr-path.hh
  • src/nix/flake-diff-locks.cc
  • src/nix/meson.build
  • CLAUDE.md
  • src/nix/develop.cc
  • src/libcmd/installable-flake.cc
  • src/libflake/include/nix/flake/meson.build
  • src/libflake/include/nix/flake/settings.hh
  • maintainers/format.sh
  • src/libflake/input-attr-path.cc
  • src/nix/flake-command.hh
  • src/libflake/lockfile-v7.cc
  • src/libflake/meson.build
  • src/nix/flake-prefetch-inputs.cc
  • src/libflake/flake-impl.hh
  • src/libflake/lockfile-v8.cc
  • src/libcmd/flake-schemas.cc
  • src/nix/flake.cc
  • src/libflake/include/nix/flake/flake.hh
  • src/libflake/flake.cc

@github-actions
github-actions Bot temporarily deployed to pull request August 5, 2026 17:50 Inactive
Reading or creating version 8 (sparse) lock files now requires the
`lock-file-v8` experimental feature. The checks are in
parseLockFileV8() and lockFlakeV8(), so they also cover transitive
version 8 lock files encountered during evaluation and commands like
`nix flake diff-locks`. Version 7 lock files are unaffected.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flake-regression-test Run the flake regressions test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant