Skip to content

Conversation

@edolstra
Copy link
Collaborator

@edolstra edolstra commented Sep 12, 2025

Motivation

Previously we always tried to substitute first (by calling ensurePath()). This wasn't a problem before lazy trees, because we always end up copying to the store anyway. But that's not the case with lazy trees. So frequently we ended up substituting an input that we had already fetched.

This showed up as

fetching source from https://cache.nixos.org

for inputs that you could swear Nix had already fetched just before. This was especially a problem for Nixpkgs inputs, since many Nixpkgs revisions are in cache.nixos.org.

Note that this could also be a problem without lazy trees, e.g. with a local input (like a Nixpkgs clone) that happens to be in the binary cache.

So we now only try substitution as a last resort, if we cannot fetch the input normally.

Context

Summary by CodeRabbit

  • Bug Fixes

    • Improved flake input resolution: reuses existing store paths when possible, falls back to substituters on fetch failures, and preserves cached fingerprints to avoid redundant work.
    • Clearer warnings and error messages during substitution and fetch failures.
  • Tests

    • Added a functional test validating flake input substitution, in-store reuse, and cache-based fallback; included in the flakes test suite.

@coderabbitai
Copy link

coderabbitai bot commented Sep 12, 2025

Walkthrough

Refactors Input::getAccessorUnchecked to separate store-path reuse and substitution from fetch logic, adding makeStoreAccessor and staged fetch/substitute flow; adds a new flake substitution functional test and registers it; adds a clarifying comment in disableNet() about local substituters.

Changes

Cohort / File(s) Summary
Fetcher control-flow refactor
`src/libfetchers/fetchers.cc`
Decouples substitution/caching from fetch; introduces `std::optional storePath` and `makeStoreAccessor()`; reuses existing store trees when possible; wraps `scheme->getAccessor` in try/catch and falls back to substitution on fetch failure; centralizes fingerprint handling and adjusts logging.
Flakes substitution tests
`tests/functional/flakes/meson.build`, `tests/functional/flakes/substitution.sh`
Adds a new functional test `substitution.sh` covering in-store reuse, uploading to a local substituter, and expected failure without substituters; registers the test in the flakes meson suite.
Config comment only
`src/nix/main.cc`
Adds a comment in `disableNet()` noting local substituters (e.g., file:///) should not be disabled; no functional change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Input
  participant Store as "Nix Store"
  participant Scheme as "Fetch Scheme"

  Caller->>Input: getAccessorUnchecked()
  alt precomputed storePath + isFinal + narHash
    Input->>Store: ensurePath(storePath)
    Store-->>Input: ok / error
    alt ok
      Input->>Input: makeStoreAccessor(storePath) (set fingerprint, display)
      Input-->>Caller: SubstitutedSourceAccessor
    else error
      Input->>Input: proceed to fetch
    end
  else
    Input->>Input: proceed to fetch
  end

  rect rgba(200,230,255,0.25)
    Note right of Input: Attempt fetch via scheme
    Input->>Scheme: getAccessor(input)
    Scheme-->>Input: accessor or throw
  end

  alt fetch succeeded
    Input-->>Caller: accessor (fingerprint propagated)
  else fetch failed
    alt storePath exists
      Input->>Store: ensurePath(storePath)
      alt substitution succeeded
        Input->>Input: makeStoreAccessor(storePath)
        Input-->>Caller: SubstitutedSourceAccessor (warning logged)
      else substitution failed
        Input-->>Caller: rethrow original fetch error (substitution error swallowed)
      end
    else
      Input-->>Caller: rethrow original fetch error
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title is a clear, concise single sentence that accurately states the PR’s primary behavioral change—attempting substitution only after a fetch failure—so it reflects the changes described in the diff and PR objectives and is understandable to reviewers scanning history.

Poem

I hop the paths where hashes hide,
Try fetching first, then look inside.
If flakes have fled, the store remains,
A cache restores what loss contains.
I thump a tune of fingerprints new—
Build on, brave devs, carrot for you! 🥕

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch substitute-inputs-on-failure-only

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/libfetchers/fetchers.cc (1)

355-383: Fallback substitution flow looks good; minor polish

  • Consider also caching the fingerprint on the returned Input when you synthesize it for the accessor to keep symmetry with the else-branch. Low risk, but keeps invariants tighter.
  • The success log on substitution might be less noisy as info/notice rather than warn, given this is an expected fallback.
-        if (!accessor->fingerprint)
-            accessor->fingerprint = result.getFingerprint(store);
+        if (!accessor->fingerprint) {
+            accessor->fingerprint = result.getFingerprint(store);
+            result.cachedFingerprint = accessor->fingerprint;
+        }
         else
             result.cachedFingerprint = accessor->fingerprint;
tests/functional/flakes/substitution.sh (2)

7-9: Make lazy-trees skip check robust

The current string compare may be brittle. Query JSON and test explicitly.

-if [[ $(nix config show lazy-trees) = true ]]; then
+if nix config show --json | jq -e '.["lazy-trees"] == true' >/dev/null; then
     exit 0
 fi

25-28: Pre-create local cache dir to avoid tool/version differences

Some versions expect the directory to exist. Harmless if it already does.

 cache="file://$TEST_ROOT/binary-cache"
-nix copy --to "$cache" "$path1"
+mkdir -p "$TEST_ROOT/binary-cache"
+nix copy --to "$cache" "$path1"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ccbe84 and 565c79c.

📒 Files selected for processing (3)
  • src/libfetchers/fetchers.cc (1 hunks)
  • tests/functional/flakes/meson.build (1 hunks)
  • tests/functional/flakes/substitution.sh (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/libfetchers/fetchers.cc (4)
src/libfetchers/path.cc (2)
  • store (120-159)
  • store (120-120)
src/libflake/flake.cc (2)
  • getFingerprint (968-991)
  • getFingerprint (968-968)
src/libfetchers/git.cc (2)
  • getFingerprint (885-914)
  • getFingerprint (885-885)
src/libfetchers/filtering-source-accessor.cc (2)
  • getFingerprint (62-67)
  • getFingerprint (62-62)
tests/functional/flakes/substitution.sh (1)
tests/functional/flakes/common.sh (2)
  • createFlake1 (51-56)
  • createFlake2 (58-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build_aarch64-darwin / build
  • GitHub Check: build_x86_64-linux / build
🔇 Additional comments (2)
src/libfetchers/fetchers.cc (1)

346-353: Nice early return when a valid store path exists

Reusing the already-valid store path for final inputs keeps control flow simple and avoids redundant fetches.

tests/functional/flakes/meson.build (1)

38-39: Test registration LGTM

Including substitution.sh in the flakes suite looks correct.

…ails

Previously we always tried to substitute first (by calling
`ensurePath()`). This wasn't a problem before lazy trees, because we
always end up copying to the store anyway. But that's not the case
with lazy trees. So frequently we ended up substituting an input that
we had already fetched.

This showed up as

  fetching source from https://cache.nixos.org

for inputs that you could swear Nix had already fetched just
before. This was especially a problem for Nixpkgs inputs, since many
Nixpkgs revisions are in cache.nixos.org.

Note that this could also be a problem without lazy trees, e.g. with a
local input (like a Nixpkgs clone) that happens to be in the binary
cache.

So we now only try substitution as a last resort, if we cannot fetch
the input normally.
@edolstra edolstra force-pushed the substitute-inputs-on-failure-only branch from 565c79c to 3fddd14 Compare September 12, 2025 12:14
@github-actions
Copy link

@github-actions github-actions bot temporarily deployed to pull request September 12, 2025 12:16 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 565c79c and 3fddd14.

📒 Files selected for processing (4)
  • src/libfetchers/fetchers.cc (1 hunks)
  • src/nix/main.cc (1 hunks)
  • tests/functional/flakes/meson.build (1 hunks)
  • tests/functional/flakes/substitution.sh (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/nix/main.cc
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/functional/flakes/substitution.sh
  • src/libfetchers/fetchers.cc
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build_x86_64-linux / manual
  • GitHub Check: build_x86_64-linux / test
  • GitHub Check: build_x86_64-linux / vm_tests_smoke
  • GitHub Check: build_aarch64-darwin / build
🔇 Additional comments (1)
tests/functional/flakes/meson.build (1)

38-38: New flakes test registered — LGTM.

Including substitution.sh in the flakes suite aligns with the PR objective. Commas and structure look correct.

// Fall back to substitution.
try {
store->ensurePath(*storePath);
warn(
Copy link
Member

Choose a reason for hiding this comment

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

Should this really be warn level? IMO, probably would be fine as debug level instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I don't think we want to quietly swallow a fetcher error since the user may want to know about it so they can fix it.

Copy link
Member

@cole-h cole-h left a comment

Choose a reason for hiding this comment

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

Otherwise lgtm

@edolstra edolstra added this pull request to the merge queue Sep 12, 2025
Merged via the queue into main with commit c2251f0 Sep 12, 2025
35 checks passed
@edolstra edolstra deleted the substitute-inputs-on-failure-only branch September 12, 2025 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants