Skip to content

fix(hooks): try python before python3 under Git for Windows so the Store alias never runs (#4563) - #4568

Open
Deepjyoti-Sarmah wants to merge 2 commits into
Graphify-Labs:v8from
Deepjyoti-Sarmah:fix/4563-windows-python3-stub
Open

Deepjyoti-Sarmah wants to merge 2 commits into
Graphify-Labs:v8from
Deepjyoti-Sarmah:fix/4563-windows-python3-stub

Conversation

@Deepjyoti-Sarmah

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #4563. When the hook's interpreter probes 1 to 4 all miss, its last resort tried python3 before python. On Windows 10/11, python3 is usually the Microsoft Store App Execution Alias in %LOCALAPPDATA%\Microsoft\WindowsApps, because python.org's installer ships python.exe but no python3.exe. Running the alias opens a console window for about 1.1 s and exits 9009. The hook then falls through to the real python, so it works, but every commit, checkout and merge flashes a window and blocks before the detached rebuild starts.

The last resort is reached when the pinned interpreter, .graphify_python, the launcher and the uv-tools scan all miss: a removed venv, graphify moved to another env, or a GUI client with a minimal PATH.

Under Git for Windows the last resort now tries python first. The Windows check is the one hooks.py already uses for its pipe-handle handling.

 # Last resort: try python3 / python (works for system/venv installs on PATH).
+# Under Git for Windows `python3` is usually the Microsoft Store App Execution Alias
+# ... Try `python` first there (#4563).
 if [ -z "$GRAPHIFY_PYTHON" ]; then
-    if command -v python3 >/dev/null 2>&1 && python3 -c "$_GFY_PROBE" 2>/dev/null; then
-        GRAPHIFY_PYTHON="python3"
-    elif command -v python >/dev/null 2>&1 && python -c "$_GFY_PROBE" 2>/dev/null; then
-        GRAPHIFY_PYTHON="python"
+    if [ -n "${WINDIR:-}" ] || [ -n "${MSYSTEM:-}" ]; then
+        _GFY_FIRST="python"; _GFY_SECOND="python3"
+    else
+        _GFY_FIRST="python3"; _GFY_SECOND="python"
+    fi
+    if command -v "$_GFY_FIRST" >/dev/null 2>&1 && "$_GFY_FIRST" -c "$_GFY_PROBE" 2>/dev/null; then
+        GRAPHIFY_PYTHON="$_GFY_FIRST"
+    elif command -v "$_GFY_SECOND" >/dev/null 2>&1 && "$_GFY_SECOND" -c "$_GFY_PROBE" 2>/dev/null; then
+        GRAPHIFY_PYTHON="$_GFY_SECOND"
     else

Probes 1 to 4, the probe command, the interpreter names, the "could not locate a Python" message and exit 0 are unchanged. All four generated hooks splice in the same _PYTHON_DETECT, so one edit covers post-commit, post-checkout, post-merge and post-rewrite.

I did not take the issue's second suggestion, skipping a python3 under */WindowsApps/*. A real Store Python also lives there, so skipping it would remove the last resort for Store-Python users.

Before (v8 probe block, a fake WindowsApps/python3 that logs, sleeps 1 s and exits 9009, ahead of a real python):

calls:  python3 (stub), python
chose:  python          1.07 s

After (MSYSTEM=MINGW64):

calls:  python
chose:  python

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Tests or CI
  • Refactor
  • Security fix

Verification & Invariants

Under Git for Windows, the hook never runs python3 while python can import graphify. Everywhere else the probe order is unchanged.

  • Read the CONTRIBUTING.md guide.
  • Reproduced the issue and identified the invariant.
  • Made the smallest fix necessary.
  • Added a regression test (if bug fix) or isolated boundary test.
  • Kept the PR description synchronized with the final implementation.
  • Documented any limitations / unsupported cases explicitly.

Merge Danger

Door: two-way. A probe-order change inside a generated hook script. No schema, cache or file-format change. Existing installs pick it up on the next graphify hook install.

Blast Radius: narrow. Only the last-resort branch changes, and only when WINDIR or MSYSTEM is set. Linux and macOS keep python3 then python. If WINDIR or MSYSTEM leaks into a non-Windows shell (for example WSL with WSLENV), the only effect is that python is tried before python3.

Limitations

  • Not run on Windows. The behavioural tests use sh-script fakes and skip on nt. The reporter's measurements (about 1.1 s, exit 9009, one AppInstallerPythonRedirector.exe per call) are the Windows evidence.
  • Hooks already installed keep the old order until graphify hook install is run again.
  • If python is itself a Store stub (no real Python on PATH), it still runs once before the warning.
  • If python lacks graphify, python3 is still probed after it.

How was this tested?

New tests in tests/test_hooks.py. They run the emitted _PYTHON_DETECT under a real sh, with a temp HOME so probes 1 to 4 miss, and only two fake directories on PATH: WindowsApps/python3 and bin/python. Each fake logs its call.

Test v8 This PR
test_windows_never_runs_the_store_python3_alias_when_python_works [msystem] fail: calls python3, python pass: calls python
test_windows_never_runs_the_store_python3_alias_when_python_works [windir] fail: same pass
test_windows_falls_back_to_python3_when_python_lacks_graphify fail: calls python3 pass: calls python, python3, picks python3
test_outside_windows_python3_is_still_tried_first pass pass
test_windows_with_no_working_python_still_fails_loudly_and_exits_zero pass pass
test_every_hook_embeds_the_probe_chain_unchanged (4 hooks) pass pass

The ones that pass on v8 guard existing behaviour: the Linux/macOS order, the warning path, and that every hook carries the tested block.

The same block also ran under dash (debian:stable-slim) and BusyBox ash (alpine:3.20) with podman. Both parse it, and both give the same call order as above, with and without MSYSTEM.

pytest tests/test_hooks.py -q (new tests, on v8 86c57be8)          -> 3 failed, 6 passed
pytest tests/test_hooks.py -q (this branch)                        -> 181 passed
pytest -q (full suite, --basetemp=/var/tmp)                        -> 8401 passed, 14 skipped
ruff check graphify/hooks.py tests/test_hooks.py                   -> All checks passed

Graphify-specific checklist

  • I updated generated skill artifacts (uv run python -m tools.skillgen --bless) when changing their source fragments. (N/A: no skill fragments touched.)
  • I confirmed that AST/structural extraction remains deterministic (no ambient state dependencies like ENV variables).
  • I reviewed changes for security implications (no unsafe interpolation into shell/Python).
  • I confirmed no API keys or local-only graph data are included.
  • (If applicable) I disclosed AI authorship in my commit messages.

…Labs#4563)

The last resort of the hook's interpreter probe tried `python3` before
`python`. On Windows 10 and 11, `python3` on PATH is usually the
Microsoft Store App Execution Alias, because the python.org installer
ships python.exe but no python3.exe. Running it opens a console window
for about a second and exits 9009, so every commit, checkout and merge
flashed a window and waited before the rebuild could start, whenever
the earlier probes missed.

When WINDIR or MSYSTEM is set, the last resort now tries `python`
first and `python3` second. Elsewhere the order is unchanged. The
probe, the interpreter names, the final message and the exit status
are the same, and the earlier probes are untouched.
@github-actions

Copy link
Copy Markdown

Thanks for the pull request, @Deepjyoti-Sarmah. A maintainer will review it soon.

Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions.

A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic.

@github-actions github-actions Bot added the contributor Pull request from a returning community contributor label Oct 19, 2026

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.

Not checked: tests were not run; no formal proof of the changed code.


Graphify review — findings

Fixes the hooks' last-resort Python probe to try python before python3 under Git for Windows (detected via WINDIR or MSYSTEM), so the Microsoft Store python3 alias no longer flashes a console window and exits 9009 on every commit, checkout and merge. Other platforms keep the python3, then python order, Windows still falls back to python3 when python lacks graphify, and with neither usable the hook warns and exits 0. Adds tests that run the emitted _PYTHON_DETECT under a real sh with fake interpreters on a bare PATH, using a copy of os.environ that drops only WINDIR, MSYSTEM and UV_TOOL_DIR, and checks that all four generated hooks embed the same chain.

No blocking issues surfaced.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 562 functions depend on the 303 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _last_resort_run() — 4 callers, 3 callees

Verification — 562 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 338 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

32 of 432 test file(s) selected (7%) via static blast radius.

  • tests/test_affected_cli.py — impact
  • tests/test_agents_platform.py — impact
  • tests/test_codebuddy.py — impact
  • tests/test_dedup_shrink_refuses_force_write.py — impact
  • tests/test_devin.py — impact
  • tests/test_explain_cli.py — impact
  • tests/test_explain_same_file_tie.py — impact
  • tests/test_extract_cli.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_god_nodes_cli.py — impact
  • tests/test_hollow_chunks_arm_shrink_guard.py — impact
  • tests/test_hook_chain_survives_skip.py — impact
  • tests/test_hooks.py — impact, changed-test
  • tests/test_hooks_rewrite.py — impact
  • tests/test_incomplete_build_guard.py — impact
  • tests/test_install.py — impact
  • tests/test_install_references.py — impact
  • tests/test_language_overrides.py — impact
  • tests/test_lookup_exit_codes.py — impact
  • tests/test_merge_chunks_validation.py — impact
  • tests/test_multigraph_diagnostics.py — impact
  • tests/test_no_dedup_flag.py — impact
  • tests/test_opencode_plugin_refresh.py — impact
  • tests/test_path_cli.py — impact
  • tests/test_pi_guard_install.py — impact
  • tests/test_query_cli.py — impact
  • tests/test_query_induced_edges.py — impact
  • tests/test_query_seeds.py — impact
  • tests/test_skill_auto_refresh.py — impact
  • tests/test_truncation_full_answer.py — impact
  • tests/test_uninstall_purge_safety.py — impact
  • tests/test_unverified_semantic_shrink.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Risky patterns (advisory)

  • medium no-stderr-to-stdout in graphify/hooks.py:142: stderr redirected to stdout: if command -v "$_GFY_FIRST" >/dev/null 2>&1 && "$_GFY_FIRST" -c "$_GFY_PROBE" 2>/dev/null; then
  • medium no-stderr-to-stdout in graphify/hooks.py:144: stderr redirected to stdout: elif command -v "$_GFY_SECOND" >/dev/null 2>&1 && "$_GFY_SECOND" -c "$_GFY_PROBE" 2>/dev/null; then

· 1 grounded finding(s) anchored inline below.

Comment thread tests/test_hooks.py

# ── #4563: the last-resort probe must not run the Microsoft Store python3 alias first on Windows ──

def _last_resort_run(tmp_path, env_extra=None, python3="stub", python="real"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — _last_resort_run()

high coupling complexity (Ca·Ce = 12).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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

contributor Pull request from a returning community contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hooks: last-resort python3 probe launches the Microsoft Store redirector on Windows (visible console flash, exit 9009)

1 participant