Skip to content

fix(setup-windows): auto-reexec under pwsh when running on Windows PowerShell 5.1 (BUG-005)#58

Merged
mlorentedev merged 1 commit into
mainfrom
fix/BUG-005-setup-ps7-reexec
May 19, 2026
Merged

fix(setup-windows): auto-reexec under pwsh when running on Windows PowerShell 5.1 (BUG-005)#58
mlorentedev merged 1 commit into
mainfrom
fix/BUG-005-setup-ps7-reexec

Conversation

@mlorentedev
Copy link
Copy Markdown
Owner

@mlorentedev mlorentedev commented May 19, 2026

Summary

  • Closes BUG-005 — Windows PowerShell 5.1 silently skipping the Merge-ClaudeSettings template merge introduced by SDD-002 (PR feat(setup): track ai/claude/settings.json template + per-key merge (SDD-002) #51). The function uses ConvertFrom-Json -AsHashtable, a PS 7+ only parameter; on PS 5.1 the wide try/catch misclassifies the ParameterBindingException as a JSON parse error and returns early.
  • Adds a preamble at the top of setup-windows.ps1 (right after param()) that detects $PSVersionTable.PSVersion.Major -lt 7. If pwsh (7+) is on PATH, re-execs the script under pwsh with $PSCommandPath @args and exits with the child's exit code. Otherwise fails loud with winget install Microsoft.PowerShell hint + exit 1.
  • Windows-only. setup-linux.sh uses bash + jq, no shell-version coupling — verified untouched (negative-parity bats assert locks this in).

Root cause

PowerShell -ExecutionPolicy Bypass -File .\setup-windows.ps1 resolves PowerShell to Windows PowerShell 5.1 on a stock Windows machine. Merge-ClaudeSettings then calls ConvertFrom-Json -AsHashtable which fails with ParameterBindingException. The function's try { ... } catch { Write-Warn "...not valid JSON..."; return } catches everything and prints the misleading "not valid JSON after placeholder substitution: A parameter cannot be found that matches parameter name 'AsHashtable'" warning. Empirically observed 2026-05-19 in setup output on a Windows 11 22631.6931 admin machine.

Test plan

  • 5 new bats asserts in tests/setup-windows.bats (PSVersion detection, pwsh re-exec branch, fail-loud branch + exit 1, BUG-005 cite in inline comment, negative parity on setup-linux.sh)
  • Smoke under Windows PowerShell 5.1: first output line is [INFO] Windows PowerShell 5.1.22621.6931 detected; re-executing under pwsh (...) for full feature compatibility (BUG-005). Setup proceeds under pwsh; settings.json merged (1687 bytes, 14 plugins + preserved user customizations); zero AsHashtable warnings.
  • Smoke under pwsh 7.6.1 direct: preamble no-op (verified during BUG-004 smoke same day, no [INFO] ... detected line appeared)
  • PSScriptAnalyzer clean (4 new PSAvoidUsingWriteHost warnings, style-consistent with existing wrappers; zero new rule classes; zero Errors)
  • git diff main..HEAD --stat -- setup-linux.sh empty (Linux untouched)
  • CI: GitGuardian, integration (Docker Ubuntu 24.04), lint, lint-powershell, test (bats) — pending CI run

Design decisions (in verification.md)

  • Auto re-exec instead of in-place PS 5.1 compatibilityMerge-ClaudeSettings could have been rewritten to convert PSCustomObject to hashtable recursively for PS 5.1 compat (~30 lines of subtle recursion vs ~15 lines of preamble). Chose re-exec because Microsoft is sunsetting Windows PowerShell 5.1 and locking the script to pwsh at the front door lets downstream code freely use modern PS features (ternary, null-conditional, etc.) without future-bug risk.
  • Write-Host instead of Write-Info wrapper — the wrapper functions are defined LATER in the script. Preamble runs before any function definition; Write-Host is structurally required. Resulting PSScriptAnalyzer warnings are consistent with the rest of the script.
  • @args instead of $PSBoundParameters — current param() is empty; positional @args suffices. Inline comment specifies $PSBoundParameters is the correct forwarding mechanism if named params are added later.
  • Don't auto-install pwsh — leaves the install action explicit to the user. Actionable error message is the better UX.

References

…werShell 5.1 (BUG-005)

SDD-002 (PR #51) introduced Merge-ClaudeSettings which uses
`ConvertFrom-Json -AsHashtable` -- a parameter added in PowerShell 7.0 that
does NOT exist in Windows PowerShell 5.1. The natural Windows command
`PowerShell -ExecutionPolicy Bypass -File .\setup-windows.ps1` resolves
`PowerShell` to 5.1 on a stock Windows machine, the Merge function's wide
try/catch swallows the ParameterBindingException as if it were a JSON parse
error ("not valid JSON after placeholder substitution: A parameter cannot be
found that matches parameter name 'AsHashtable'"), and the per-key merge from
ai/claude/settings.json is silently skipped. Existing ~/.claude/settings.json
survives untouched, so the bug is invisible until a fresh-machine bootstrap
depends on the template merge.

Implementation: preamble block after param() and before CONFIGURATION header
detects $PSVersionTable.PSVersion.Major < 7. If `Get-Command pwsh` resolves
to PowerShell 7+, re-exec under it with `-NoProfile -ExecutionPolicy Bypass
-File $PSCommandPath @args` and exit with the child's exit code. Otherwise,
print an actionable [ERROR] line pointing at `winget install Microsoft.PowerShell`
and exit 1 (fail-loud, no silent skip).

Verification:
- Smoke under Windows PowerShell 5.1.22621.6931: first output line is
  `[INFO] Windows PowerShell 5.1.22621.6931 detected; re-executing under
  pwsh (...) for full feature compatibility (BUG-005)`. Setup proceeds under
  pwsh, settings.json merged correctly (1687 bytes, 14 plugins + preserved
  user customizations), zero AsHashtable warnings.
- Smoke under pwsh 7.6.1 direct: preamble no-op, no `[INFO] ... detected`
  lines, existing behavior preserved (verified during BUG-004 smoke same day).
- pwsh-missing branch: not directly testable (pwsh installed on dev machine);
  structurally locked by bats grep asserting `winget install Microsoft.PowerShell`
  literal + `exit 1`.
- 5 new bats asserts in tests/setup-windows.bats; RED -> GREEN via grep-by-grep
  emulation.
- PSScriptAnalyzer: 4 new Write-Host warnings, style-consistent with existing
  Write-Info/Success/Warn/Err wrappers (those wrappers are themselves
  Write-Host calls; preamble cannot use them because the wrappers are defined
  below the preamble's runtime position). Zero Errors. Zero new rule classes.

Linux side (setup-linux.sh) is immune by construction (bash + jq, no
shell-version coupling); zero changes there. Negative-parity bats assert
locks this in.

Spec: specs/BUG-005-setup-ps7-reexec/
Vault: 10_projects/dotfiles/11-tasks.md (entry added in same session)
Sibling: PR #57 (BUG-004 claude-mem-truncate-guard)
Upstream: https://learn.microsoft.com/powershell/scripting/whats-new/what-s-new-in-powershell-70

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@mlorentedev mlorentedev force-pushed the fix/BUG-005-setup-ps7-reexec branch from 0d69424 to d711db0 Compare May 19, 2026 19:29
@mlorentedev mlorentedev merged commit e677c9b into main May 19, 2026
5 checks passed
@mlorentedev mlorentedev deleted the fix/BUG-005-setup-ps7-reexec branch May 19, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant