Skip to content

Invalidate the eval cache when local path: inputs change#2929

Open
domenkozar wants to merge 3 commits into
mainfrom
fix/path-input-eval-cache
Open

Invalidate the eval cache when local path: inputs change#2929
domenkozar wants to merge 3 commits into
mainfrom
fix/path-input-eval-cache

Conversation

@domenkozar

Copy link
Copy Markdown
Member

Summary

Fixes edits to local path: inputs (e.g. url: path:../shared-config) never taking effect: the evaluation cache served the old configuration until .devenv was deleted, and devenv update did not help. Reported in But our devenv is in another repo.

Root cause

Local path: inputs were materialized with fetchTree into an immutable store copy, and nothing content-derived made it into the eval cache key:

  1. Nix strips narHash (and other volatile attributes) from local inputs when writing devenv.lock, and the locker never computes one for relative path inputs in the first place.
  2. The lock fingerprint (added in ee845f4 specifically to detect changes to unlocked inputs) is computed from the on-disk lock.
  3. A path input without a narHash has no fingerprint, so it is silently skipped.

With the cache key constant and evaluation reading from the store copy (ignored by input tracking as immutable), nothing ever invalidated.

Fix

resolve-lock.nix now resolves path inputs without a pinned narHash to the live filesystem path instead of a fetchTree store copy. The eval cache then tracks every file the evaluation actually reads (module imports, readFile, pathExists probes), so:

  • edits invalidate the cache precisely on the next command
  • direnv picks the files up via input-paths.txt and reloads on changes
  • the input tree (including its .git) is no longer copied to the store on every cache miss

outPath stays path-typed so string interpolation still copies to the store with context, keeping derivation references to input files working. Inputs that do carry a narHash keep going through fetchTree, so pinned snapshots remain verified.

The new integration test also flushed out a second, pre-existing cache bug: a tracked path that was missing during evaluation (e.g. a probed devenv.local.nix) and created within the same second as the snapshot was declared unchanged by the mtime shortcut in check_file_state. A stored input with no content hash that is readable now is always treated as modified.

Test plan

  • New integration test tests/eval-cache-path-input: sibling config repo via path: input; asserts edit invalidation, input tracking, and devenv.local.nix appearance (all three steps run sub-second, covering the mtime collision).
  • New unit tests in devenv-eval-cache for the missing-then-created file and directory cases (46/46 pass).
  • Existing coverage of the changed resolution path: eval-cache-git, nixpkgs-config (relative path input with ?dir=), and the overlays example (flake: true path:./subflake) all pass.
  • devenv-nix-backend unit tests 33/33, fmt and clippy clean.

Known gaps (follow-ups, pre-existing)

  • Files referenced only via path interpolation into derivations ("${./file}") do not invalidate the eval cache; this affects the project root identically (CopiedSource ops are not reaching the input tracker).
  • git+file:// local inputs have the same staleness for the same reason (also isLocal, rev/narHash stripped) and need a different treatment.

🤖 Generated with Claude Code

domenkozar and others added 2 commits June 10, 2026 23:31
…issing

A tracked path that did not exist during evaluation (e.g. a
devenv.local.nix probed via pathExists) is stored with no content hash
and a fallback snapshot timestamp instead of a real mtime. When the file
was later created within the same second as the snapshot, the mtime
equality shortcut in check_file_state declared it unchanged and the
cache went stale.

A stored input with no content hash that is readable now is always a
modification: existence itself is the change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Local path: inputs were materialized with fetchTree into an immutable
store copy, and nothing content-derived made it into the eval cache key:
Nix strips narHash from local inputs when writing devenv.lock, the lock
fingerprint is computed from the on-disk lock, and a path input without
a narHash has no fingerprint. Edits to the input directory were
invisible until .devenv was deleted, and devenv update did not help.

Resolve path inputs without a pinned narHash to the live filesystem
path instead. The eval cache then tracks every file the evaluation
actually reads (module imports, readFile, pathExists probes), so edits
invalidate it precisely, direnv watches the input's files via
input-paths.txt, and the input tree is no longer copied to the store on
every cache miss.

outPath stays path-typed so string interpolation still copies to the
store with context, keeping derivation references to input files
working. Inputs that do carry a narHash keep going through fetchTree so
pinned snapshots remain verified.

Reported in https://evantravers.com/articles/2026/06/10/but-our-devenv-is-in-another-repo/

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploying devenv with  Cloudflare Pages  Cloudflare Pages

Latest commit: d148a54
Status: ✅  Deploy successful!
Preview URL: https://8afffebc.devenv.pages.dev
Branch Preview URL: https://fix-path-input-eval-cache.devenv.pages.dev

View logs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant