Skip to content

CI: resolve the NuGet cache key once, so the post step never walks the tree - #3231

Merged
mgravell merged 1 commit into
mainfrom
marc/ci-nuget-cache-key
Sep 17, 2026
Merged

mgravell merged 1 commit into
mainfrom
marc/ci-nuget-cache-key

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

The Windows job has been failing at teardown, after everything real has already passed:

The template is not valid. .github/workflows/CI.yml (Line: 86, Col: 16):
hashFiles('Directory.Packages.props, global.json, **/*.csproj') failed.
Fail to hash files under directory 'D:\a\StackExchange.Redis\StackExchange.Redis'

Which step

Not the one the line number suggests. From the failing job on #3228:

 4  Cache NuGet packages       -> success     <- restore, on a still checkout
 8  .NET Build                 -> success
10  StackExchange.Redis.Tests  -> success
23  Post Cache NuGet packages  -> failure     <- here

actions/cache re-evaluates key in its post step to decide what to save, and that runs at job
teardown. Both evaluations point at line 86, which is why the error reads as though the restore failed.

Why #3209 did not finish the job

That change moved the hash ahead of the redis servers so the restore would see a still tree, and that half
works — step 4 passes. But the post step re-walks the workspace after sixteen redis processes have been
writing through DrvFs and every project has grown bin/ and obj/ across six target frameworks. Same
"enumerating a tree that is changing underneath you" the existing comment describes, at the point where the
churn is worst.

The change

Resolve the expression once into a step output and hand actions/cache a plain string:

- name: Compute NuGet cache key
  id: nuget-cache-key
  run: |
    "value=${{ runner.os }}-nuget-${{ hashFiles('Directory.Packages.props', 'global.json', '**/*.csproj') }}" >> $env:GITHUB_OUTPUT
- name: Cache NuGet packages
  uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: ${{ steps.nuget-cache-key.outputs.value }}
    restore-keys: ${{ runner.os }}-nuget-

The glob now runs exactly where it is safe — straight after checkout — and the post step has nothing left to
enumerate. Cache behaviour is otherwise unchanged: same key, same restore-keys.

Left in pwsh to match the rest of the job, which has no defaults.run.shell and inherits the Windows
runner's default.

Notes

The failure is intermittent, so a green run here does not by itself prove the fix — what does is that the
post step no longer evaluates hashFiles at all, which is visible in the diff rather than in a result. The
Compute NuGet cache key step appearing before Cache NuGet packages, and the post step no longer failing,
are the things to look for.

The alternative would be continue-on-error: true on the cache step, which hides the symptom and silently
stops saving the cache; this removes the cause.

…e tree

The Windows job has been failing at teardown with

  The template is not valid. .github/workflows/CI.yml (Line: 86, Col: 16):
  hashFiles('Directory.Packages.props, global.json, **/*.csproj') failed.
  Fail to hash files under directory 'D:\a\StackExchange.Redis\StackExchange.Redis'

after every real step had already passed - build, tests and all. The failing
step is "Post Cache NuGet packages": actions/cache re-evaluates `key` in its post
step to decide what to save, and that runs at the end of the job rather than the
beginning.

#3209 moved the hash ahead of the redis servers so the restore would see a still
tree, and that half works - the restore step passes. But the post step re-walks
the workspace once sixteen redis processes have been writing through DrvFs and
every project has grown bin/ and obj/ across six target frameworks, which is the
same "enumerating a tree that is changing underneath you" the existing comment
describes, only worse.

Resolve the expression once into a step output and hand actions/cache a plain
string. The glob then runs exactly where it is safe - straight after checkout -
and the post step has nothing left to enumerate.

Left in pwsh to match the rest of the job, which has no defaults.run.shell and
so inherits the Windows runner's default.
@mgravell
mgravell merged commit 1a4874c into main Sep 17, 2026
6 checks passed
@mgravell
mgravell deleted the marc/ci-nuget-cache-key branch September 17, 2026 06:04
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