From b75c7ce751b1d1691cf3fb4d32800fb3ac952604 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 17 Sep 2026 06:33:50 +0100 Subject: [PATCH] CI: resolve the NuGet cache key once, so the post step never walks the 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. --- .github/workflows/CI.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8178c671b..f4c6a6a6a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -72,18 +72,26 @@ jobs: Write-Output "Computed package version: $version" # Restore is ~80s of the build step on a cold runner, and the package set changes rarely. # - # Keep this immediately after checkout, and *before* the WSL/redis steps. The key globs the whole - # workspace for **/*.csproj, and once the servers are up there are sixteen redis processes writing - # into that same tree through DrvFs - rdb files into RedisConfigs/Temp, nodes-*.conf into - # RedisConfigs/Cluster, and BGSAVE's temp-.rdb appearing and being renamed under the walk. - # Enumerating a tree that is changing underneath you is how you get an intermittent - # "hashFiles(...) failed. Fail to hash files under directory ...", which is exactly what this job - # started doing. Nothing here needs redis, so hash a still checkout instead. + # The key globs the whole workspace for **/*.csproj, so it must only ever be computed against a + # still tree. Two things disturb one: the sixteen redis processes writing through DrvFs once the + # servers are up - rdb files into RedisConfigs/Temp, nodes-*.conf into RedisConfigs/Cluster, and + # BGSAVE's temp-.rdb appearing and being renamed under the walk - and, later, every bin/ and + # obj/ the build produces. Enumerating a tree that is changing underneath you is how you get an + # intermittent "hashFiles(...) failed. Fail to hash files under directory ...". + # + # Hashing here, straight after checkout, keeps the restore safe; but actions/cache re-evaluates + # `key` in its post step to decide what to save, and that runs at the end of the job when the + # churn is at its worst. So resolve it *once*, into an output, and hand the cache action a plain + # string it can reuse at both ends. + - 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: ${{ runner.os }}-nuget-${{ hashFiles('Directory.Packages.props', 'global.json', '**/*.csproj') }} + key: ${{ steps.nuget-cache-key.outputs.value }} restore-keys: ${{ runner.os }}-nuget- - uses: Vampire/setup-wsl@v7 # v7 is the first version running on Node 24 with: