Skip to content

fix(powershell): probe python3 before selecting it in Get-Python3Command - #4151

Open
jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/ps-probe-python3
Open

jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/ps-probe-python3

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Problem

Get-Python3Command's first branch returns @('python3') on mere Get-Command presence, with no execution probe — while its own second and third branches do probe:

if (Get-Command python3 -ErrorAction SilentlyContinue) { return @('python3') }   # <-- no probe
if (Get-Command python  -ErrorAction SilentlyContinue) {
    $ver = & python --version 2>&1
    if ($ver -match 'Python 3') { return @('python') }
}

Its own docstring promises "a usable Python 3 executable".

On Windows, python3 almost always resolves to the Microsoft Store App Execution Alias stub, which Get-Command finds but which fails at runtime:

found=True
source=C:\Users\...\AppData\Local\Microsoft\WindowsApps\python3.exe
ver=[Python was not found; run without arguments to install from the Microsoft Store, ...]
LASTEXITCODE=9009
match=False

Note the existing probe already rejects it correctly — the first branch just never runs one.

Precedent in this repo

scripts/bash/common.sh documents this exact hazard by name and defends against it:

"on Windows python3 commonly resolves to the Microsoft Store App Execution Alias stub, which passes command -v but fails at runtime (exit 49), so an availability-gated elif would pick python3, swallow its failure, and never reach the fallback — leaving feature.json unreadable even though it is valid (issue #3304)."

Its _python3_command probes all three candidates. The PowerShell twin probes only the last two.

Second half of the same root cause

The existing probes use & python --version 2>&1. In Windows PowerShell, redirecting a native command's stderr into the success stream wraps each line in an ErrorRecord — so under the $ErrorActionPreference = 'Stop' that every caller sets, the probe raises a terminating NativeCommandError rather than simply failing the match. Reaching branch 2 therefore crashes outright when python is also a Store alias (the Windows 11 default). One function, one fix.

Reproduction — shimmed interpreters on PATH, against upstream/main

python3 = dead stub, python = working  ->  RESULT=[python3]          <-- picks the dead stub
python  = dead stub, nothing else      ->  THREW: RemoteException    <-- probe crashes

Callers (Resolve-TemplateContent → setup-plan.ps1, create-new-feature.ps1) then invoke the dead stub, whose stderr becomes a terminating error under 'Stop', aborting the script instead of falling through to a working interpreter.

With the fix:

python3 = dead stub, python = working  ->  RESULT=[python]
python  = dead stub, nothing else      ->  RESULT=[]        (clean $null, no throw)
python3 + python dead, py -3 working   ->  RESULT=[py -3]

Fix

Extract the probe into Test-Python3Command and apply it to all three branches. The probe saves and restores $ErrorActionPreference around the invocation so a candidate writing to stderr fails the match instead of throwing.

Match semantics are unchanged — still -match 'Python 3', exactly as branches 2 and 3 already did.

Verification

  • Fail-before / pass-after: 3 new-vs-baseline failures with common.ps1 reverted to upstream/main → 3 passed with the fix.
  • Tests are self-contained: they shim a fake Store stub (stderr + exit 9009) and a working interpreter onto PATH, so they do not depend on the runner actually having a Store alias. Gated on HAS_POWERSHELL.
  • Broader PowerShell surface (9 script/parity test modules): 80 passed, 96 skipped, plus one failure — test_setup_tasks_ps_core_template_resolved — which is pre-existing on clean main, fails identically with and without this change, and has an unrelated cause (JSONDecodeError: Invalid control character in emitted stdout). I am not claiming to fix it.
  • common.ps1 remains ASCII-only (verified byte-wise: 0 non-ASCII bytes), per this repo's .ps1 encoding rule and tests/test_ps1_encoding.py, which passes.
  • uvx ruff@0.15.0 check src tests → clean

No breaking change. A genuinely working python3 still passes the probe and is still selected first; only candidates that cannot actually run are now skipped — which is what the function already promised.


Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current main.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The probe ignores nonzero exits, and the new .cmd-based tests fail on POSIX systems with PowerShell.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds runtime validation when selecting Python 3 for PowerShell scripts.

Changes:

  • Centralizes interpreter probing across python3, python, and py -3.
  • Adds Store-alias fallback tests.
File summaries
File Description
scripts/powershell/common.ps1 Adds and applies the Python probe.
tests/test_resolve_template_python_parity.py Tests unusable interpreter fallbacks.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/powershell/common.ps1 Outdated
Comment thread tests/test_resolve_template_python_parity.py Outdated
@mnriem

mnriem commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Nice work, and thanks for disclosing the AI assistance. Copilot's review flagged two things worth addressing before I merge: the probe should check the exit code (not just the Python 3 string match, to match the bash contract), and the .cmd-shim tests need to skip on non-Windows even when pwsh is present. Re-request review once those are in.

@mnriem mnriem added author-awaiting Waiting on author response triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review author-over-cap Over the 3-open-PR cap or repetitive batch submissions — please consolidate labels Sep 8, 2026
@mnriem

mnriem commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Thanks for your contributions here, @jawwad-ali — a quick note on review prioritization. You currently have 6 open pull requests, above the three-open-PR guideline in CONTRIBUTING. This isn't a freeze — your open PRs stay in the queue and can still be reviewed and merged. But beyond three, additional submissions may be placed behind other contributors' work, so the most effective path is to consolidate where you can, or tell us the few you'd most like prioritized, and I'll focus on those first. I'm adding the author-over-cap label here purely as a tracking marker; no action needed beyond letting us know your priority order.

(Drafted with AI assistance — GitHub Copilot.)

@mnriem mnriem removed the author-over-cap Over the 3-open-PR cap or repetitive batch submissions — please consolidate label Sep 12, 2026
@mnriem
mnriem requested a balanced review from Copilot September 14, 2026 21:23
@mnriem

mnriem commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks @jawwad-ali— the probe now captures $LASTEXITCODE immediately and requires success, which addresses that part of the review.

The POSIX shim change still has a problem: the driver replaces PATH with the shim directory, while every POSIX shim uses #!/usr/bin/env sh. There is no sh in that directory, so even the working fake interpreter fails to launch. The equivalent launch exits 127 with env: sh: No such file or directory.

Please make the POSIX shims runnable under that restricted PATH—for example, use an absolute shell interpreter—while keeping real Python installations excluded from the test. Then verify the success and fallback cases on POSIX PowerShell and request re-review.

Drafted for @mnriem by GitHub Copilot (model: GPT-6 Astra).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation addresses the reported failure modes with focused cross-platform regression coverage.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@mnriem mnriem added the author-needs-rebase Branch conflicts with main — rebase/resolve before merge label Sep 21, 2026

@mnriem mnriem left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix test & lint errors

@mnriem mnriem mentioned this pull request Oct 5, 2026
4 of 5 tasks
`Get-Python3Command` returned `python3` on mere `Get-Command` presence, with
no execution probe -- unlike its own `python` and `py -3` branches. On
Windows `python3` almost always resolves to the Microsoft Store App Execution
Alias stub, which `Get-Command` finds but which fails at runtime, so callers
invoked a dead interpreter instead of falling through to a working one. The
Bash twin (`_python3_command`) already probes all three candidates.

The existing `python` / `py -3` probes had a second problem with the same
root: `& python --version 2>&1` under the `$ErrorActionPreference = 'Stop'`
that every caller sets raises a terminating NativeCommandError when the
probed exe writes to stderr, rather than simply failing the match.

`Test-Python3Command` now probes each fallback candidate without throwing, and
requires a zero exit status as well as a "Python 3" banner -- the Bash twin
gates purely on exit status, so a wrapper that echoes a version and then
fails must not be selected. The `SPECKIT_PYTHON_EXECUTABLE` override block
that main added above these branches is left unchanged.

Tests shim fake interpreters onto PATH in both forms -- an executable script
always, plus a `.cmd` on Windows -- because `HAS_POWERSHELL` is also true for
`pwsh` on Linux and macOS. The scripts use an absolute `#!/bin/sh`: the driver
replaces PATH with the shim directory alone, so `#!/usr/bin/env sh` cannot
find `sh` and every shim exits 127, which is why these tests failed on macOS
CI while passing on Windows. The driver also strips the SPECKIT_PYTHON
override variables, which would otherwise bypass the shims entirely.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jawwad-ali
jawwad-ali force-pushed the fix/ps-probe-python3 branch from 843f825 to 5554b20 Compare October 5, 2026 16:18
@jawwad-ali

Copy link
Copy Markdown
Contributor Author

@mnriem — fixed, and rebased onto current main as a single commit (5554b20).

The test failure was real, and it was mine. The three get_python3 selection tests failed on pytest (macos-latest, 3.13):

test_get_python3_command_skips_unusable_python3   assert '' == 'python'

Root cause: the test driver replaces PATH with the shim directory alone, and the POSIX shims used #!/usr/bin/env sh — so env couldn't find sh and every shim exited 127, leaving nothing selectable. On Windows the .cmd shims are used, which is why it passed locally. Reproduced the mechanism directly:

PATH=<shim dir only>  #!/usr/bin/env sh  -> /usr/bin/env: 'sh': No such file or directory  (exit 127)
PATH=<shim dir only>  #!/bin/sh          -> Python 3.12.0                                   (exit 0)

Shims now use an absolute #!/bin/sh. I executed every shim body under that same restricted PATH to confirm each behaves as designed (dead stub fails, working interpreter prints a version and exits 0, py only succeeds with -3, the lying wrapper prints a version but exits 1).

Rebase: main added a SPECKIT_PYTHON_EXECUTABLE / SPECKIT_PYTHON override block above these branches (#4445). I left it untouched and only replaced the three unprobed fallbacks. Because that override is consulted first and clean_env() strips only SPECIFY_*, the test driver now also pops both override variables — otherwise a runner with either set would bypass the shims and test the override instead. Verified the tests stay green with SPECKIT_PYTHON_EXECUTABLE set in the environment.

Gates on the rebased branch: 4 selection tests fail with common.ps1 at upstream/main → 4 pass with the fix; parity + PowerShell suites 46 passed; ruff check src tests clean (the earlier lint errors were conflict markers from the stale base).

@mnriem
mnriem requested a balanced review from Copilot October 5, 2026 16:27
@mnriem mnriem removed author-awaiting Waiting on author response author-needs-rebase Branch conflicts with main — rebase/resolve before merge labels Oct 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The generated test driver uses ASCII encoding, causing the new tests to fail when embedded paths contain non-ASCII characters.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread tests/test_resolve_template_python_parity.py Outdated
The probe driver embeds `shim_dir` and the `common.ps1` path into
single-quoted PowerShell literals and was written as ASCII, so:

- a non-ASCII path (username, temp dir or checkout containing e.g. `é`)
  raised UnicodeEncodeError before PowerShell started, and
- a path containing `'` (e.g. a `C:\Users\O'Brien` profile) terminated the
  literal early and failed with a PowerShell parse error.

Double `'` the way the sibling parity tests already do, and write the driver
as UTF-8 with a BOM so Windows PowerShell 5.1 does not read it in the ANSI
code page. Reproduced both with `--basetemp` under such paths: 4 failed
before, 4 passed after (also with both characters in one path).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants