diff --git a/components/lefthook-powershell/README.md b/components/lefthook-powershell/README.md index 911acdd5..fd1875f0 100644 --- a/components/lefthook-powershell/README.md +++ b/components/lefthook-powershell/README.md @@ -14,9 +14,18 @@ PSScriptAnalyzer policy remains owned by the `psscriptanalyzer` component; this adapter only shortens local feedback. It launches one fresh `pwsh -NoProfile -NonInteractive` worker per target, analyzes that target exactly once, continues through later targets to report all failures, and treats every -analyzer engine/rule error as a failed hook. This process boundary avoids -PSScriptAnalyzer 1.25.0's intermittent cross-target engine-state failure without -retrying, suppressing, or accepting a different result. +analyzer engine/rule error as a failed hook. No target is retried. + +PSScriptAnalyzer 1.25.0 runs rules in parallel against a shared CommandInfo +cache, so a single file in a fresh process can intermittently throw a +`NullReferenceException` ([PowerShell/PSScriptAnalyzer#1867][3], [#1708][4]). +The per-target worker keeps one failure from masking other targets; it does +not prevent the race. Until the upstream fix ([#2206][5]) ships in a release, +remove the triggers: keep `PSUseCorrectCasing` disabled, and drop +`Export-ModuleMember` from a `.psm1` that exports every function and alias it +defines (without the call, a script module exports all its functions and +aliases). A module that hides private members keeps the call, or moves its +export list into a module manifest. The focused `psscriptanalyzer-staged.test.ps1` regression supplies a fake analyzer module to prove deterministic one-target/one-process isolation, then @@ -29,3 +38,6 @@ single-target [`Invoke-ScriptAnalyzer -Path ... -Settings ...` interface][2]. [1]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_pwsh [2]: https://learn.microsoft.com/powershell/module/psscriptanalyzer/invoke-scriptanalyzer +[3]: https://github.com/PowerShell/PSScriptAnalyzer/issues/1867 +[4]: https://github.com/PowerShell/PSScriptAnalyzer/issues/1708 +[5]: https://github.com/PowerShell/PSScriptAnalyzer/pull/2206 diff --git a/components/lefthook-powershell/fixtures/get-command/CommandLookup.psm1 b/components/lefthook-powershell/fixtures/get-command/CommandLookup.psm1 index dd07bd7c..1dbe5ed3 100644 --- a/components/lefthook-powershell/fixtures/get-command/CommandLookup.psm1 +++ b/components/lefthook-powershell/fixtures/get-command/CommandLookup.psm1 @@ -13,5 +13,3 @@ function Get-ExternalToolPath { } return $null } - -Export-ModuleMember -Function Get-ExternalToolPath diff --git a/components/lefthook-powershell/psscriptanalyzer-staged.ps1 b/components/lefthook-powershell/psscriptanalyzer-staged.ps1 index c89d496a..5ab9837a 100644 --- a/components/lefthook-powershell/psscriptanalyzer-staged.ps1 +++ b/components/lefthook-powershell/psscriptanalyzer-staged.ps1 @@ -12,8 +12,10 @@ Lefthook runs) and passes it explicitly: Invoke-ScriptAnalyzer does not reliably auto-discover a root settings file when the analyzed path is in a subdirectory, so without this the consumer's ruleset would be silently ignored. Each target runs exactly once in its own fresh no-profile pwsh - worker. PSScriptAnalyzer 1.25.0 can leak engine state between sequential targets in one process and - intermittently throw a NullReferenceException even though every target passes in isolation. + worker. PSScriptAnalyzer 1.25.0 runs rules in parallel against a shared cached CommandInfo, which + intermittently throws a NullReferenceException on files that call Export-ModuleMember or when + PSUseCorrectCasing is enabled (PowerShell/PSScriptAnalyzer#1867, #1708; fixed upstream in #2206). + A fresh worker per target keeps one failure from masking other targets; it does not prevent the race. The PSScriptAnalyzer component owns the rules; this adapter only orchestrates isolated invocations. CI remains the authoritative gate and this lane is fast staged-file feedback. diff --git a/components/lefthook-powershell/psscriptanalyzer-staged.test.ps1 b/components/lefthook-powershell/psscriptanalyzer-staged.test.ps1 index 3993ac17..479c1fab 100644 --- a/components/lefthook-powershell/psscriptanalyzer-staged.test.ps1 +++ b/components/lefthook-powershell/psscriptanalyzer-staged.test.ps1 @@ -141,9 +141,9 @@ Export-ModuleMember -Function Invoke-ScriptAnalyzer ) Assert-Condition ($historicalHookFiles.Count -eq 6) 'The historical commit-hook regression must use six files.' - # The PSScriptAnalyzer 1.25.0 cross-target state failure is intermittent. Repeating the exact - # six-file hook shape made the old shared-process adapter fail reliably, while the fake-module - # assertion above deterministically proves each target now receives an isolated worker. + # The PSScriptAnalyzer 1.25.0 CommandInfo race is intermittent. Repeating the exact six-file hook + # shape made the old shared-process adapter fail reliably, while the fake-module assertion above + # deterministically proves each target now receives its own worker. foreach ($iteration in 1..8) { $real = Invoke-AdapterProcess -Files $historicalHookFiles $realSucceeded = $real.ExitCode -eq 0 diff --git a/components/lefthook-powershell/psscriptanalyzer-target.ps1 b/components/lefthook-powershell/psscriptanalyzer-target.ps1 index 7ee2081f..5ca55836 100644 --- a/components/lefthook-powershell/psscriptanalyzer-target.ps1 +++ b/components/lefthook-powershell/psscriptanalyzer-target.ps1 @@ -3,9 +3,8 @@ .SYNOPSIS Analyzes one PowerShell target in an isolated process for the staged-file adapter. .DESCRIPTION - This internal worker is launched once per target by psscriptanalyzer-staged.ps1. Process - isolation prevents PSScriptAnalyzer engine state from leaking between targets while preserving - fail-closed behavior: analyzer errors and findings both return nonzero, and no target is retried. + This internal worker is launched once per target by psscriptanalyzer-staged.ps1. It is + fail-closed: analyzer errors and findings both return nonzero, and no target is retried. .PARAMETER Target The one PowerShell file to analyze. .PARAMETER Settings diff --git a/components/psscriptanalyzer/README.md b/components/psscriptanalyzer/README.md index 6ebd2082..f99e01e4 100644 --- a/components/psscriptanalyzer/README.md +++ b/components/psscriptanalyzer/README.md @@ -25,5 +25,5 @@ Lefthook adapter invokes every staged target exactly once in a distinct process, continues after an engine error, and fails the overall hook. It then repeatedly starts the exact historical six-file no-profile hook contract, followed by the current staged set, against pinned PSScriptAnalyzer 1.25.0. This covers both the -`PSUseCorrectCasing` exclusion for issue #1708 and the separate intermittent -cross-target engine-state failure that requires process isolation. +`PSUseCorrectCasing` exclusion for issue #1708 and the intermittent CommandInfo +race (issue #1867), which a per-target worker contains but does not prevent.