mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-13 02:47:00 +00:00
Merge pull request #1266 from PowPickles/fix/claude-cli-windows-spawn
fix: claude-cli backend works headlessly on Windows npm installs
This commit is contained in:
+27
-3
@@ -321,6 +321,18 @@ def _bedrock_inference_config(max_tokens: int, model: str = "") -> dict:
|
||||
return cfg
|
||||
|
||||
|
||||
def _no_window_kwargs() -> dict:
|
||||
"""subprocess kwargs that suppress the console window claude.cmd would
|
||||
otherwise pop on Windows. A labeling/extraction run spawns one `claude -p`
|
||||
per batch — with Windows Terminal as the default terminal each spawn
|
||||
becomes a visible window that appears and vanishes for the duration of the
|
||||
model call. CREATE_NO_WINDOW keeps the children invisible; no-op elsewhere."""
|
||||
import subprocess
|
||||
if sys.platform == "win32":
|
||||
return {"creationflags": subprocess.CREATE_NO_WINDOW}
|
||||
return {}
|
||||
|
||||
|
||||
def _resolve_api_timeout(default: float = 600.0) -> float:
|
||||
"""Honour GRAPHIFY_API_TIMEOUT env var override, else use default (seconds)."""
|
||||
raw = os.environ.get("GRAPHIFY_API_TIMEOUT", "").strip()
|
||||
@@ -1095,6 +1107,7 @@ def _call_claude_cli(user_message: str, max_tokens: int = 8192, *, deep_mode: bo
|
||||
encoding="utf-8", # Force UTF-8 — prevents UnicodeEncodeError on Windows cp1252
|
||||
timeout=_resolve_api_timeout(),
|
||||
check=False,
|
||||
**_no_window_kwargs(),
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(
|
||||
@@ -1743,17 +1756,28 @@ def _call_llm(prompt: str, *, backend: str, max_tokens: int = 200) -> str:
|
||||
return resp.content[0].text if resp.content else ""
|
||||
|
||||
if backend == "claude-cli":
|
||||
import shutil, subprocess
|
||||
if shutil.which("claude") is None:
|
||||
import platform, shutil, subprocess
|
||||
# Mirror the extraction-path resolution: on Windows the npm shim is
|
||||
# claude.cmd, which CreateProcess can't resolve from a bare "claude"
|
||||
# (PATHEXT doesn't apply), so pass the resolved .cmd path explicitly.
|
||||
claude_cmd = "claude"
|
||||
if platform.system() == "Windows":
|
||||
cmd_path = shutil.which("claude.cmd")
|
||||
if cmd_path:
|
||||
claude_cmd = cmd_path
|
||||
elif shutil.which("claude") is None:
|
||||
raise RuntimeError("Claude Code CLI not found on $PATH")
|
||||
elif shutil.which("claude") is None:
|
||||
raise RuntimeError("Claude Code CLI not found on $PATH")
|
||||
proc = subprocess.run(
|
||||
["claude", "-p", "--output-format", "json", "--no-session-persistence"],
|
||||
[claude_cmd, "-p", "--output-format", "json", "--no-session-persistence"],
|
||||
input=prompt,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8", # Force UTF-8 — prevents UnicodeEncodeError on Windows cp1252
|
||||
timeout=_resolve_api_timeout(),
|
||||
check=False,
|
||||
**_no_window_kwargs(),
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"claude -p exited {proc.returncode}: {proc.stderr.strip()[:500]}")
|
||||
|
||||
@@ -228,3 +228,30 @@ def test_call_llm_claude_cli_branch_honours_timeout(monkeypatch, fake_claude):
|
||||
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "30")
|
||||
llm._call_llm(prompt="x", backend="claude-cli", max_tokens=10)
|
||||
assert fake_claude.call_args.kwargs["timeout"] == 30.0
|
||||
|
||||
|
||||
def test_simple_completion_resolves_cmd_shim_on_windows(monkeypatch):
|
||||
"""The label/_simple_completion path must spawn the resolved claude.cmd on
|
||||
Windows; a bare "claude" fails CreateProcess (WinError 2) under npm installs."""
|
||||
import json as _json
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_run(args, **kwargs):
|
||||
captured["argv0"] = args[0]
|
||||
proc = MagicMock()
|
||||
proc.returncode = 0
|
||||
proc.stdout = _json.dumps({"result": "ok"})
|
||||
return proc
|
||||
|
||||
def fake_which(name):
|
||||
return r"C:\npm\claude.cmd" if name == "claude.cmd" else r"C:\npm\claude"
|
||||
|
||||
with patch("platform.system", return_value="Windows"), \
|
||||
patch("shutil.which", side_effect=fake_which), \
|
||||
patch("subprocess.run", side_effect=fake_run):
|
||||
out = llm._call_llm("hi", backend="claude-cli")
|
||||
|
||||
assert out == "ok"
|
||||
assert captured["argv0"] == r"C:\npm\claude.cmd"
|
||||
|
||||
Reference in New Issue
Block a user