Skip to content

[Windows] _call_claude_cli fails with WinError 2 — subprocess.run cannot execute claude.ps1 (proposed fix included) #1072

Description

@dougwmorrow

Summary

On Windows, --backend claude-cli fails for every chunk with [WinError 2] The system cannot find the file specified, because _call_claude_cli in graphify/llm.py invokes subprocess.run(["claude", ...]) and Windows' CreateProcess cannot directly execute the claude.ps1 script that npm installs. The bare-name claude resolves via PATHEXT — and on systems where .PS1 is listed in PATHEXT ahead of .CMD, Python subprocess.run resolves to the PowerShell script and crashes. claude.cmd (also installed by npm) IS directly executable by CreateProcess and works fine.

Result: --backend claude-cli is broken on Windows for a non-trivial fraction of installs. All N semantic chunks fail with the same WinError 2, then graphify aborts with all semantic chunks failed for backend 'claude-cli'.

Environment

  • OS: Windows 11 Home 10.0.26200
  • Python: 3.12.12 (uv-bundled)
  • Graphify: v0.8.24 (installed via uv tool install git+https://github.com/safishamsi/graphify)
  • Claude Code: v2.1.142
  • npm claude install layout (typical):
    • C:\Users\<user>\AppData\Roaming\npm\claude (shell shim, not Windows-executable)
    • C:\Users\<user>\AppData\Roaming\npm\claude.cmd (Windows batch wrapper, subprocess-executable)
    • C:\Users\<user>\AppData\Roaming\npm\claude.ps1 (PowerShell script, NOT subprocess-executable)
  • shutil.which("claude") returns claude.ps1 on this machine (PATHEXT prefers .PS1).

Reproduction

# Prereqs: claude CLI installed + authenticated; graphify installed; ANTHROPIC_API_KEY NOT needed for claude-cli backend
$env:GRAPHIFY_CLAUDE_CLI_MODEL = "claude-sonnet-4-6"
mkdir _gtest
copy README.md _gtest\
graphify extract _gtest --backend claude-cli --max-concurrency 1

Actual output

[graphify extract] scanning C:\...\_gtest
[graphify extract] found 0 code, 1 docs, 0 papers, 0 images
[graphify extract] semantic extraction on 1 files via claude-cli...
[graphify] chunk 1/1 failed: [WinError 2] The system cannot find the file specified
[graphify] WARNING: 1/1 semantic chunk(s) failed — see errors above. Partial results returned.
[graphify extract] error: all semantic chunks failed for backend 'claude-cli' (1 uncached files) - see per-chunk errors above. If you see 'requires the X package', run `pip install X` and retry.

Expected output

Same successful extraction the macOS/Linux paths produce — chunk 1/1 done, graph artifacts written to _gtest/graphify-out/.

Root cause

graphify/llm.py::_call_claude_cli (around line 510):

cli_args = [
    "claude", "-p",
    "--output-format", "json",
    "--no-session-persistence",
    "--system-prompt", _extraction_system(deep=deep_mode),
]
# ...
proc = subprocess.run(cli_args, ..., capture_output=True, ...)

subprocess.run(["claude", ...]) on Windows hands the list to CreateProcess, which:

  1. Resolves claude via PATH + PATHEXT
  2. On many Windows configs, PATHEXT lists .PS1 (added by PowerShell Core / Claude installer / user customization), causing resolution to claude.ps1
  3. CreateProcess cannot execute .ps1 files directly — needs explicit powershell.exe -File
  4. Returns ERROR_FILE_NOT_FOUND → Python raises FileNotFoundError([WinError 2])

When PATHEXT happens to put .CMD ahead of .PS1 (or .PS1 is absent), claude.cmd resolves first and the path works. The bug is conditional on PATHEXT ordering.

Proposed fix

Prefer the claude.cmd Windows batch wrapper explicitly on Windows. It's installed alongside claude.ps1 by npm and is CreateProcess-executable. Minimal patch in _call_claude_cli:

 def _call_claude_cli(user_message: str, max_tokens: int = 8192, *, deep_mode: bool = False) -> dict:
+    import platform
     import shutil
     import subprocess

     if shutil.which("claude") is None:
         raise RuntimeError(
             "Claude Code CLI not found on $PATH. Install from "
             "https://claude.ai/code and run `claude` once to authenticate."
         )

+    # Windows: subprocess.run cannot directly execute claude.ps1. Prefer
+    # claude.cmd (npm also installs it) which CreateProcess handles fine.
+    if platform.system() == "Windows":
+        claude_exec = shutil.which("claude.cmd") or shutil.which("claude")
+    else:
+        claude_exec = "claude"

     cli_args = [
-        "claude", "-p",
+        claude_exec, "-p",
         "--output-format", "json",
         "--no-session-persistence",
         "--system-prompt", _extraction_system(deep=deep_mode),
     ]

Verified locally: applying the above to a v0.8.24 install lets a 185KB single-file extract succeed via --backend claude-cli (158 nodes, 142 edges, 37 communities, $0 incremental cost via subscription).

Workaround (without the patch)

Set PATHEXT in the shell that invokes graphify so .CMD resolves before .PS1:

$env:PATHEXT = ".CMD;.EXE;.BAT;.PS1;" + $env:PATHEXT
graphify extract ... --backend claude-cli

This is fragile (depends on user shell config and is non-obvious) — the in-code fix is the proper solution.

Cross-references

Happy to open a PR if useful. Reproduces 100% on Windows installs where PATHEXT lists .PS1 before .CMD.

Activity

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