diff --git a/src/libflake/flake.cc b/src/libflake/flake.cc index 3a9bdf43a..46b8e6a28 100644 --- a/src/libflake/flake.cc +++ b/src/libflake/flake.cc @@ -37,7 +37,19 @@ static StorePath copyInputToStore( auto narHash = state.store->queryPathInfo(storePath)->narHash; input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true)); - assert(!originalInput.getNarHash() || storePath == originalInput.computeStorePath(*state.store)); + if (originalInput.getNarHash() && storePath != originalInput.computeStorePath(*state.store)) { + throw Error( + "NAR hash mismatch for flake input '%s':\n" + " expected: %s (store path: %s)\n" + " got: %s (store path: %s)\n" + "This typically happens when the content at the specified path has changed since the NAR hash was recorded.", + input.to_string(), + originalInput.getNarHash()->to_string(HashFormat::SRI, true), + originalInput.computeStorePath(*state.store).to_string(), + narHash.to_string(HashFormat::SRI, true), + storePath.to_string() + ); + } return storePath; } diff --git a/tests/functional/flakes/meson.build b/tests/functional/flakes/meson.build index 801fefc6f..b087e407a 100644 --- a/tests/functional/flakes/meson.build +++ b/tests/functional/flakes/meson.build @@ -34,6 +34,7 @@ suites += { 'source-paths.sh', 'old-lockfiles.sh', 'trace-ifd.sh', + 'nar-hash-mismatch.sh', ], 'workdir': meson.current_source_dir(), } diff --git a/tests/functional/flakes/nar-hash-mismatch.sh b/tests/functional/flakes/nar-hash-mismatch.sh new file mode 100755 index 000000000..52e6d2291 --- /dev/null +++ b/tests/functional/flakes/nar-hash-mismatch.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +source ./common.sh + +requireGit + +clearStore +clearCache + +# Create a test flake with NAR hash mismatch +tmpDir=$TEST_ROOT/nar-hash-test +rm -rf "$tmpDir" +mkdir -p "$tmpDir" +cd "$tmpDir" + +# Setup git repo with a sub-flake +initGitRepo . +mkdir sub +echo '{ outputs = { self }: { test = "hello"; }; }' > sub/flake.nix +git add sub +git commit -m "add sub" + +# Get the original hash and create main flake that references it +hash=$(nix hash path ./sub) +echo "$hash" > sub.narHash + +cat > flake.nix << EOF +{ + outputs = { self }: + let + hash = builtins.readFile ./sub.narHash; + cleanHash = builtins.substring 0 (builtins.stringLength hash - 1) hash; + subFlake = builtins.getFlake "path:\${toString ./sub}?narHash=\${cleanHash}"; + in + { inherit (subFlake) test; }; +} +EOF + +git add flake.nix sub.narHash + +# Modify sub-flake to create hash mismatch +echo '{ outputs = { self }: { test = "modified"; }; }' > sub/flake.nix + +# Test that evaluation fails with proper error message (not assertion failure) +if output=$(nix eval .#test 2>&1); then + fail "Expected evaluation to fail, but it succeeded" +fi + +# Verify error message contains expected content and no crash indicators +grepQuiet "NAR hash mismatch" <<< "$output" || fail "Expected 'NAR hash mismatch' in error output" +grepQuietInverse "Assertion.*failed" <<< "$output" || fail "Should not contain assertion failure" +grepQuietInverse "Aborted" <<< "$output" || fail "Should not contain 'Aborted'" \ No newline at end of file