← back to Cli Printing Press
fix(cli): skip Windows Python Store stub in verify-skill (#1086)
27ad2dda618d56431cf3fcd826017c3a760c3cc2 · 2026-05-11 10:22:37 -0700 · Trevin Chow
* fix(cli): skip Windows Python Store stub in verify-skill
Windows verify-skill crashed before any check ran because
exec.LookPath("python3") resolves to the Microsoft Store launcher stub
(.../WindowsApps/python3.exe) by default. The stub is on PATH but exits
with "Python was not found" instead of executing the script.
Add a resolvePython() fallback chain that's a no-op on non-Windows
(returns "python3") and on Windows skips the stub via path-based
detection, preferring the Python Launcher ("py") and "python" before
falling back. isWindowsStorePython matches WindowsApps + python in the
resolved path; LookPath cannot distinguish the stub any other way.
Sub-bugs 2 (UTF-8 file decode) and 3 (UTF-8 stdout) from #876 were
already resolved by #985 (read_utf8 helper + pythonUTF8Env env vars).
Closes #876
* test(cli): pin versioned Microsoft Store stub case for isWindowsStorePython
Locks in the existing substring match against a future refactor that
might tighten basename detection to strict python.exe / python3.exe
matches and silently reintroduce the bug on Windows 11 where the Store
launcher exposes python3.13.exe under the same WindowsApps directory.
Files touched
M internal/cli/verify_skill.goM internal/cli/verify_skill_internal_test.go
Diff
commit 27ad2dda618d56431cf3fcd826017c3a760c3cc2
Author: Trevin Chow <trevin@trevinchow.com>
Date: Mon May 11 10:22:37 2026 -0700
fix(cli): skip Windows Python Store stub in verify-skill (#1086)
* fix(cli): skip Windows Python Store stub in verify-skill
Windows verify-skill crashed before any check ran because
exec.LookPath("python3") resolves to the Microsoft Store launcher stub
(.../WindowsApps/python3.exe) by default. The stub is on PATH but exits
with "Python was not found" instead of executing the script.
Add a resolvePython() fallback chain that's a no-op on non-Windows
(returns "python3") and on Windows skips the stub via path-based
detection, preferring the Python Launcher ("py") and "python" before
falling back. isWindowsStorePython matches WindowsApps + python in the
resolved path; LookPath cannot distinguish the stub any other way.
Sub-bugs 2 (UTF-8 file decode) and 3 (UTF-8 stdout) from #876 were
already resolved by #985 (read_utf8 helper + pythonUTF8Env env vars).
Closes #876
* test(cli): pin versioned Microsoft Store stub case for isWindowsStorePython
Locks in the existing substring match against a future refactor that
might tighten basename detection to strict python.exe / python3.exe
matches and silently reintroduce the bug on Windows 11 where the Store
launcher exposes python3.13.exe under the same WindowsApps directory.
---
internal/cli/verify_skill.go | 40 +++++++++++++++++++++++++++++-
internal/cli/verify_skill_internal_test.go | 30 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/internal/cli/verify_skill.go b/internal/cli/verify_skill.go
index 2f4e05d4..e2df6258 100644
--- a/internal/cli/verify_skill.go
+++ b/internal/cli/verify_skill.go
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "runtime"
"strings"
"github.com/mvanhorn/cli-printing-press/v4/internal/generator"
@@ -25,6 +26,12 @@ var verifySkillScript string
const canonicalSectionsCheckName = "canonical-sections"
+const (
+ pythonPython3 = "python3"
+ pythonPy = "py"
+ pythonPython = "python"
+)
+
type verifySkillRunResult struct {
Stdout string
Stderr string
@@ -71,7 +78,7 @@ func runVerifySkillScript(dir string, only []string, asJSON bool, strict bool) (
pyArgs = append(pyArgs, "--strict")
}
- py := exec.Command("python3", pyArgs...)
+ py := exec.Command(resolvePython(), pyArgs...)
py.Stdin = os.Stdin
py.Env = pythonUTF8Env(os.Environ())
var stdout, stderr bytes.Buffer
@@ -335,6 +342,37 @@ func emitMergedJSON(pyResult verifySkillRunResult, runCanonical, hasCanonicalFin
return nil
}
+// resolvePython picks the interpreter name to pass to exec.Command for the
+// embedded verify-skill script. The Windows fallback chain exists because
+// exec.LookPath("python3") on Windows often resolves to the Microsoft Store
+// launcher stub, which is on PATH but exits with a "Python was not found"
+// Store-redirect message instead of running the script.
+func resolvePython() string {
+ if runtime.GOOS != "windows" {
+ return pythonPython3
+ }
+ if path, err := exec.LookPath(pythonPython3); err == nil && !isWindowsStorePython(path) {
+ return pythonPython3
+ }
+ if _, err := exec.LookPath(pythonPy); err == nil {
+ return pythonPy
+ }
+ if path, err := exec.LookPath(pythonPython); err == nil && !isWindowsStorePython(path) {
+ return pythonPython
+ }
+ return pythonPython3
+}
+
+// LookPath succeeds against the Microsoft Store Python launcher stub at
+// .../WindowsApps/python*.exe, so the only pre-exec signal is the path itself.
+func isWindowsStorePython(path string) bool {
+ if path == "" {
+ return false
+ }
+ lower := strings.ToLower(path)
+ return strings.Contains(lower, "windowsapps") && strings.Contains(lower, "python")
+}
+
// pythonUTF8Env returns base with PYTHONIOENCODING and PYTHONUTF8 forced to
// UTF-8. Windows consoles default to cp1252, which cannot encode the ✓/✘
// glyphs the Python script prints; without these env vars the subprocess
diff --git a/internal/cli/verify_skill_internal_test.go b/internal/cli/verify_skill_internal_test.go
index 7302cb86..c12585be 100644
--- a/internal/cli/verify_skill_internal_test.go
+++ b/internal/cli/verify_skill_internal_test.go
@@ -64,3 +64,33 @@ func TestPythonUTF8Env_EmptyBase(t *testing.T) {
t.Errorf("expected both UTF-8 entries, got %v", got)
}
}
+
+func TestIsWindowsStorePython(t *testing.T) {
+ t.Parallel()
+
+ cases := []struct {
+ name string
+ path string
+ want bool
+ }{
+ {"store stub python3", `C:\Users\alice\AppData\Local\Microsoft\WindowsApps\python3.exe`, true},
+ {"store stub python", `C:\Users\alice\AppData\Local\Microsoft\WindowsApps\python.exe`, true},
+ {"store stub versioned python3.13", `C:\Users\alice\AppData\Local\Microsoft\WindowsApps\python3.13.exe`, true},
+ {"store stub mixed case", `C:\Users\alice\AppData\Local\Microsoft\WINDOWSAPPS\Python3.exe`, true},
+ {"store stub forward slashes", `C:/Users/alice/AppData/Local/Microsoft/WindowsApps/python3.exe`, true},
+ {"real python install", `C:\Python314\python.exe`, false},
+ {"py launcher", `C:\Windows\py.exe`, false},
+ {"unix path", "/usr/bin/python3", false},
+ {"unrelated windowsapps binary", `C:\Users\alice\AppData\Local\Microsoft\WindowsApps\winget.exe`, false},
+ {"empty path", "", false},
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ if got := isWindowsStorePython(tc.path); got != tc.want {
+ t.Errorf("isWindowsStorePython(%q) = %v, want %v", tc.path, got, tc.want)
+ }
+ })
+ }
+}
← 46481718 docs(skills): clarify wrapper-only catalog entries have no g
·
back to Cli Printing Press
·
fix(catalog): refresh google-flights entry to reflect fli na 5c4ef3a7 →