feat(scripts): detect PowerShell 7-only syntax in hook manifest commands - #2757
Open
Jamie Kim (jkim323) wants to merge 3 commits into
Open
feat(scripts): detect PowerShell 7-only syntax in hook manifest commands#2757Jamie Kim (jkim323) wants to merge 3 commits into
Jamie Kim (jkim323) wants to merge 3 commits into
Conversation
Contributor
Eval Execution |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2757 +/- ##
==========================================
+ Coverage 82.98% 83.06% +0.07%
==========================================
Files 183 169 -14
Lines 33787 32879 -908
Branches 25 0 -25
==========================================
- Hits 28038 27310 -728
+ Misses 5746 5569 -177
+ Partials 3 0 -3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…t-ps51-compat # Conflicts: # .github/hooks/shared/telemetry.json
Katrien De Graeve (katriendg)
approved these changes
Aug 24, 2026
Katrien De Graeve (katriendg)
left a comment
Contributor
There was a problem hiding this comment.
Thank you, this implementation looks complete.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Description
Test-HookManifestnow rejects PowerShell 7-only syntax in anypowershellhook command, so a manifest that Windows PowerShell 5.1 hosts cannot parse fails validation instead of failing silently at runtime.Why this guard exists
The repository pins PowerShell 7 everywhere it controls invocation — 90 files declare
#Requires -Version 7.4, every npm script callspwsh, and CI usesshell: pwsh. Hook commands are the exception: the host chooses the interpreter, and it may launch thepowershellbranch with Windows PowerShell 5.1 (powershell.exe). A PS7-only construct there fails at parse time, before the hook script is ever reached, so the hook silently never runs and the host emits a parser error per lifecycle event.That is not hypothetical.
.github/hooks/shared/telemetry.jsonshipped exactly this defect across all 11 of its lifecycle events (#2724), and nothing in the toolchain caught it.docs/contributing/hooks.mdkeeps the hook manifest contract supported andlint:hooksruns in bothvalidate:localandplugin:validate, so any future manifest can reintroduce the same class of defect.What the check does
Detection matches token kinds from
[System.Management.Automation.Language.Parser]::ParseInput, not raw text. Operators appearing inside string literals are therefore not false-flagged, andbashcommands are never inspected.Covered constructs:
QuestionMark? :QuestionQuestion??QuestionQuestionEquals??=QuestionDot?.QuestionLBracket?[AndAnd&&OrOr||A rejected manifest reports which construct was found and why it matters, for example:
Scope change since this PR opened
This PR originally also rewrote the ternary in
.github/hooks/shared/telemetry.jsonto an equivalentif-expression. #2719 has since landed onmainand retired the telemetry hook entirely, deleting that manifest and all 17 of its implementation files. Mergingmaintherefore resolved the modify/delete conflict by accepting the deletion — the manifest cannot be fixed because it no longer exists, and restoring it would reintroduce a hook pointing at a removed collector script.The preventive half is what remains, and it was always the durable half. The original PR description anticipated this outcome:
The repository now ships zero hook manifests, so this check is purely forward-looking. It costs nothing at rest —
lint:hooksreports0 found— and it fails closed the moment someone adds a manifest with the defect that #2724 documented.Related Issue(s)
Closes #2724
Note
#2724 has two halves. The defect half (the shipped ternary) was resolved by #2719 deleting the manifest. This PR closes the recurrence half by making the toolchain reject the construct. If you would rather keep #2724 open or close it against #2719 instead, change this line before merging.
Type of Change
Select all that apply:
Code & Documentation:
Infrastructure & Configuration:
AI Artifacts:
hve-builderand addressed all actionable findings.github/instructions/*.instructions.md).github/prompts/*.prompt.md).github/agents/*.agent.md).github/skills/*/SKILL.md).github/hooks/*/*.json)evals/) (N/A — no prompt, agent, instruction, or skill behavior changed)Other:
.ps1,.sh,.py)Testing
Diff is two files, both under
scripts/:scripts/linting/Validate-HookManifests.ps1Get-HookPowerShellIncompatibilityand wires it intoTest-HookManifest(+65)scripts/tests/linting/Validate-HookManifests.Tests.ps1Describeblock (+41)Automated validation re-run locally after merging
main:npm run plugin:validatelint:hooksreports0 foundnpm run test:ps -- -TestPath "scripts/tests/linting/Validate-HookManifests.Tests.ps1"npm run test:ps -- -TestPath "scripts/tests/linting/"Invoke-ScriptAnalyzerwithscripts/linting/PSScriptAnalyzer.psd1on both changed filesEarlier targeted verification of the construct, performed against the manifest before #2719 removed it, is retained here as evidence that the detector matches real host behavior rather than an assumed grammar:
sessionStartcommand under Windows PowerShell 5.1.26100.8875 returnsFAIL: Unexpected token '?' in expression or statement.lint:hooksreportedOK.New coverage in
Validate-HookManifests.Tests.ps1(9 cases): ternary rejection, if-expression acceptance, no false positive on PS7-only operators inside string literals,bashcommands not inspected, and one case per PS7-only construct.Manual testing was not performed beyond the parser checks above.
Unrelated test failures: the 2 failures are both in
Validate-DesignIntent.Tests.ps1(inadequate-deciding-claimandschema-violationfixtures), which touches no file in this PR and is pre-existing — see #2722.Checklist
Required Checks
docs/contributing/hooks.mddocuments the manifest contract and schema, not per-construct validator rules; the rejection message is self-describing)AI Artifact Contributions
hve-builderreview mode to review contributionhve-builderreviewRequired Local Checks
The following local-safe validation commands must pass before merging:
npm run validate:local— not re-run in full after the merge; the lanes that own this diff (lint:ps,lint:hooks,lint:plugin-manifest) were run individually and pass. The prior full run failed onlylint:md-links, on two external URLs unrelated to this diff.npm run validate:docs(N/A — no docs changed)npm run spell-check— prior run reported 37 issues, all the same token, all inside the gitignoreddocs/docusaurus/test-results/**artifacts. No tracked file and no file in this diff is affected.npm run lint:md-links(N/A — no markdown changed)Security Considerations
scripts/linting/, notscripts/security/)The detector calls
Parser::ParseInput, which builds an AST and returns tokens. It does not invokeInvoke-Expression, dot-source, or otherwise execute the manifest command string, so validating an untrusted manifest does not run its contents.Additional Notes
Superseded content. The manifest edit this PR opened with is gone from the diff;
git diff origin/main HEADnow shows only the twoscripts/files. The merge commit resolving that conflict is7018fd50.Corrections to the issue body. Two items in #2724 were stale and were not actionable:
scripts/plugins/Modules/PluginHelpers.psm1andscripts/tests/plugins/PluginHelpers.Materialization.Tests.ps1as requiring sync. Both were removed by fix(build): source plugin metadata from repository root #2736 ("source plugin metadata from repository root").'.github'fallback with ahooks/...child path, which no longer matches any file onmain.