Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions components/lefthook-powershell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ function Get-ExternalToolPath {
}
return $null
}

Export-ModuleMember -Function Get-ExternalToolPath
6 changes: 4 additions & 2 deletions components/lefthook-powershell/psscriptanalyzer-staged.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions components/lefthook-powershell/psscriptanalyzer-target.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions components/psscriptanalyzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading