Skip to content

Windows: update/query fail with "can't open file" after PYTHONHASHSEED re-exec (uv tool install) #3779

Closed

Description

@shashankadhungana

What happens

On Windows, when graphify is installed with uv tool install graphifyy, every command that pins the hash seed fails immediately. That includes graphify update . and graphify query ...:

~\AppData\Roaming\uv\python\cpython-3.11-windows-x86_64-none\python.exe: can't open file '~\\.local\\bin\\graphify': [Errno 2] No such file or directory

Commands that don't re-exec, such as graphify --help, work fine.

Cause

The hash-seed pin added in #3641 re-execs like this (graphify/__main__.py, around L525):

os.execvpe(sys.executable, [sys.executable, *sys.argv], {**os.environ, "PYTHONHASHSEED": "0"})

On Windows, uv installs the console script as a launcher .exe. There, sys.argv[0] is ~\.local\bin\graphify (no extension, and not a Python file), so python <argv[0]> has nothing to run. The same will likely happen with pip/pipx console-script .exe launchers on Windows.

Suggested fix

Re-exec as a module so it doesn't depend on argv[0]:

os.execvpe(sys.executable, [sys.executable, "-m", "graphify", *sys.argv[1:]], {**os.environ, "PYTHONHASHSEED": "0"})

Workaround

Set PYTHONHASHSEED=0 before running graphify. This skips the re-exec.

Environment

  • graphifyy 0.9.66 (installed with uv tool install "graphifyy[sql]")
  • uv 0.12.5
  • Windows 11 (10.0.26220), PowerShell 5.1
  • Python 3.11 (uv-managed)

Activity

  1. zandeto commented on Sep 23, 2026

    @zandeto

    Confirming this on a pip install, and reporting a second failure mode of the same re-exec that is worse than the one above because it produces no error at all.

    Environment

    • graphifyy 0.9.66, installed with pip install --user (not uv)
    • Python 3.13.5, Windows 11 (10.0.26100), AMD64
    • Launcher: %APPDATA%\Python\Python313\Scripts\graphify.exe
    1. Your prediction about pip launchers is correct. The report expects "the same failure with pip/pipx console-script .exe launchers on Windows." It does happen. I saw the identical message:

    C:\Python313\python.exe: can't open file 'C:\Users<user>\AppData\Roaming\Python\Python313\Scripts\graphify':
    [Errno 2] No such file or directory

    Worth noting an aggravating detail specific to upgrades: 0.7.10 shipped an extensionless Scripts/graphify alongside the .exe, and 0.9.66 ships only the .exe. After upgrading, a shell that still resolves the old extensionless name hits this even when graphify.exe is present and working.

    1. The same re-exec can fail completely silently, which is the more dangerous case. In other runs the process exited 0 with empty stdout and empty stderr, wrote no files, and produced no error line. From a script or CI wrapper that is indistinguishable from "ran fine, nothing to do." One variant exited 139 instead.

    PYTHONFAULTHANDLER=1 is what surfaces the cause:

    Windows fatal exception: access violation

    Current thread 0x00003e54 (most recent call first):
    File "", line 635 in execvpe
    File "", line 621 in execvpe
    File "...\graphify_main
    .py", line 525 in pin_hash_seed_if_needed
    File "...\graphify_main
    .py", line 535 in main
    File "...\graphify_main_.py", line 800 in

    So on Windows the call does not always raise something catchable. Sometimes it takes the process down.

    1. Because of that, the existing safety net cannot fire. The docstring says the function "degrades instead of raising if the re-exec itself fails, since an unusual host that disallows it should still be able to run graphify, just without this determinism guarantee." That intent is right, but:

    try:
    os.execvpe(sys.executable, [sys.executable, *sys.argv], {**os.environ, "PYTHONHASHSEED": "0"})
    except OSError:
    pass

    An access violation is not an OSError. It terminates the interpreter, so except OSError: pass never runs and the graceful degradation does not happen.

    Suggestion for PR #3780, beyond the argv fix. Switching to [sys.executable, "-m", "graphify", *sys.argv[1:]] addresses the argv[0] problem. Consider also skipping the re-exec entirely on Windows and setting the seed in-process, or treating the pin as best-effort by checking the child actually started. Silent-success is the failure mode most likely to be mistaken for correct behaviour, and it is the one users cannot see.

    Verified workaround, for anyone hitting this before the fix lands. Set the variable yourself so the function returns early instead of re-execing. The value matches what it would have pinned, so determinism is preserved:

    PYTHONHASHSEED=0 python -m graphify cluster-only . --no-viz

    Affected commands are the members of _HASHSEED_PINNED_COMMANDS. I hit it on cluster-only; with PYTHONHASHSEED=0 set, extract, cluster-only and label all completed normally across a full rebuild.

  2. safishamsi commented on Sep 23, 2026

    @safishamsi
    Member

    Fixed in v0.9.67 via #3780 — the hash-seed pin re-execs via python -m graphify, fixing the Windows console-script launcher failure. Thanks @ayushcodes10.

  3. zandeto commented on Sep 25, 2026

    @zandeto

    Thanks for the quick fix. On 0.9.67 the launcher failure is gone: graphify --version works through the pip .exe launcher. The second failure mode from my comment above still happens, though.

    Environment: graphifyy 0.9.67, pip install --user, Python 3.13.5, Windows 11 (10.0.26100), AMD64.

    Repro: python -m graphify label --help with PYTHONFAULTHANDLER=1, 10 runs each way. PYTHONHASHSEED unset: 139 0 0 0 139 139 0 0 0 0, 3 of 10 crash. PYTHONHASHSEED=0: 0 in all 10 runs. Every crash has the same stack: main.py:810 → main (:545) → _pin_hash_seed_if_needed (:531) → os.execvpe → Windows fatal exception: access violation.

    So the -m graphify change fixed which file the new process opens, but the re-exec still goes through os.execvpe, and that call is what crashes. except OSError cannot catch it, because the process dies. A possible fix: on Windows, start the child with subprocess.run([sys.executable, "-m", "graphify", *sys.argv[1:]], env=env) and exit with its return code. Until then, PYTHONHASHSEED=0 skips the re-exec and worked in every run.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions