When a command is auto-loaded, PowerShell analyzes the modules on PSModulePath and caches the result in ModuleAnalysisCache-<hash>. The cache is written by a background task that waits 10 seconds and then until nothing has changed for 3 more seconds (AnalysisCacheData.QueueSerialization). A pwsh that runs one command and exits sooner, which is how scripts, build tools and agents use pwsh -Command / pwsh -File, never writes it. Every such process analyzes the same modules again.
On my machine the copy of 7.6.9 I used for testing had no cache file at all after hundreds of runs. With a cache pre-written by a longer session (PSModuleAnalysisCachePath), pwsh -NoProfile -NonInteractive -Command "Get-ChildItem -Name src | Select-Object -First 5" is 150 ms faster on Windows 11 (621 -> 471 ms, 95% CI 140-180) and 76 ms faster on Ubuntu 26.04 under WSL2 on the same machine (388 -> 303 ms, CI 62-97), 30 interleaved runs each. On Windows the user module path is under OneDrive (Documents redirection) and is searched first, so a few user modules (AzureAD alone 32 ms) are analyzed before the core module that has Get-ChildItem is reached.
If a save is still pending when the process exits, write the cache synchronously from a ProcessExit handler. I have a change that does this (about 20 lines in AnalysisCache.cs plus a Pester test) and can open a PR.
The issue is that today the cache never gets saved by a short-lived process at all. The write happens on a background task that waits at least 13 seconds after the last change, so pwsh -Command ... exits before it ever runs, and the next process analyzes the same modules again. On my machine that's around 100 ms per invocation that auto-loads a cmdlet (548 -> 447 ms with #28376).
With the PR, a process only writes at exit if it analyzed something that wasn't already in the cache it loaded. To check the parallel case I started 20 processes at the same time against an empty cache, and repeated that 5 times. Between 1 and 8 of them wrote the file in each round; the rest either found it already written or lost the race on the exclusive open, which PowerShell already handles (the IOException is caught, and a writer merges a newer file first). All 200 exited normally, and the file was valid every time. After that, another 100 processes started against the existing file and none of them wrote anything. With the current code the same test never produces a cache file.
The concurrent writes only happen when the cache is cold or a module changed, and they go through the same code path long-running sessions already use.
Activity
azchohfi commented on Nov 9, 2026
The issue is that today the cache never gets saved by a short-lived process at all. The write happens on a background task that waits at least 13 seconds after the last change, so pwsh -Command ... exits before it ever runs, and the next process analyzes the same modules again. On my machine that's around 100 ms per invocation that auto-loads a cmdlet (548 -> 447 ms with #28376).
With the PR, a process only writes at exit if it analyzed something that wasn't already in the cache it loaded. To check the parallel case I started 20 processes at the same time against an empty cache, and repeated that 5 times. Between 1 and 8 of them wrote the file in each round; the rest either found it already written or lost the race on the exclusive open, which PowerShell already handles (the IOException is caught, and a writer merges a newer file first). All 200 exited normally, and the file was valid every time. After that, another 100 processes started against the existing file and none of them wrote anything. With the current code the same test never produces a cache file.
The concurrent writes only happen when the cache is cold or a module changed, and they go through the same code path long-running sessions already use.