Skip to content

Telemetry hook manifest uses PowerShell 7-only ternary, breaking hosts that launch Windows PowerShell 5.1 #2724

Description

@jkim323

Summary

Every lifecycle event in .github/hooks/shared/telemetry.json declares its powershell command using the ternary operator:

& (Join-Path ([string]::IsNullOrWhiteSpace($env:CLAUDE_PLUGIN_ROOT) ? '.github' : $env:CLAUDE_PLUGIN_ROOT) 'hooks/shared/telemetry/Invoke-TelemetryCollector.ps1')

The ternary operator was introduced in PowerShell 7. When the host launches the hook with Windows PowerShell 5.1 (powershell.exe), the command fails to parse before the collector script is ever reached, and the session emits a parser error on every hook event.

Observed error

At line:1 char:69
+ ... Path ([string]::IsNullOrWhiteSpace($env:CLAUDE_PLUGIN_ROOT) ? '.githu ...
+                                                                 ~
Unexpected token '?' in expression or statement.
At line:1 char:68
+ ... n-Path ([string]::IsNullOrWhiteSpace($env:CLAUDE_PLUGIN_ROOT) ? '.git ...
+                                                                  ~
Missing closing ')' in expression.
At line:1 char:162
+ ... _PLUGIN_ROOT) 'hooks/shared/telemetry/Invoke-TelemetryCollector.ps1')
+                                                                         ~
Unexpected token ')' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedToken

Impact

  • Telemetry collection silently never runs on affected hosts; the hook dies at parse time.
  • Noisy parser errors surface in the session on every lifecycle event (11 events are declared).
  • Only affects hosts that route to the powershell branch with Windows PowerShell 5.1. Hosts using the bash branch or pwsh 7+ are unaffected, which is why this went unnoticed.
  • Invoke-TelemetryCollector.ps1 itself is already written to be 5.1-safe (it deliberately avoids 3-arg Join-Path and BOM-emitting Add-Content), so the manifest is the only 5.1-incompatible surface. docs/customization/local-telemetry.md also documents the collector as "PowerShell 5.1+", so the manifest contradicts the stated support floor.

Reproduction

Confirmed on Windows PowerShell 5.1.26100.8875:

$cmd = (Get-Content '.github/hooks/shared/telemetry.json' -Raw | ConvertFrom-Json).hooks.sessionStart[0].powershell
powershell.exe -NoProfile -Command "[System.Management.Automation.Language.Parser]::ParseInput('$cmd', [ref]$null, [ref]$null)"

Proposed fix

Replace the ternary with an equivalent if-expression, which parses identically on 5.1 and 7+ and preserves the IsNullOrWhiteSpace fallback semantics:

& (Join-Path $(if ([string]::IsNullOrWhiteSpace($env:CLAUDE_PLUGIN_ROOT)) { '.github' } else { $env:CLAUDE_PLUGIN_ROOT }) 'hooks/shared/telemetry/Invoke-TelemetryCollector.ps1')

Files that must stay in sync:

  • .github/hooks/shared/telemetry.json — all 11 lifecycle events (sessionStart, userPromptSubmitted, userPromptSubmit, preToolUse, postToolUse, subagentStart, subagentStop, sessionEnd, stop, agentStop, preCompact).
  • scripts/plugins/Modules/PluginHelpers.psm1Write-PluginHookArtifact string-matches the repository form to collapse it to & (Join-Path $env:CLAUDE_PLUGIN_ROOT '...') in materialized plugins. If the manifest changes and this transform does not, installed plugins silently keep the repository fallback.
  • scripts/tests/plugins/PluginHelpers.Materialization.Tests.ps1 — the Hook plugin root fallback suite asserts the exact repository and installed command strings.

Acceptance criteria

  • No hook command in .github/hooks/shared/telemetry.json uses PowerShell 7-only syntax.
  • The sessionStart command parses without error under Windows PowerShell 5.1.
  • Fallback semantics are unchanged: unset, empty, and whitespace-only CLAUDE_PLUGIN_ROOT all resolve to .github; an explicit root is passed through.
  • Write-PluginHookArtifact still produces the installed form, and the Hook plugin root fallback tests pass.
  • npm run lint:json and npm run plugin:validate pass.
  • Consider a guard that fails validation if a hook manifest command contains PowerShell 7-only syntax, so this cannot regress.

Notes

Existing plugin installations carry the old command and need to be regenerated or reinstalled to pick up the fix.

Metadata

Metadata

Labels

bugSomething isn't workingpackagingExtension and plugin packagingtoolingDeveloper tooling and utilities

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions