Skip to content

Commit 6cd2c72

Browse files
committed
Fix Windows WinError 2 by resolving git robustly, plus analysis fixes
The packaged Windows .exe invoked a bare "git" and died with "[WinError 2] The system cannot find the file specified" on machines without Git on PATH. New api/git_exe.py resolves git through HACKDEEPWIKI_GIT -> managed MinGit -> PATH -> common Windows install dirs, and on Windows provisions a SHA-256-verified portable MinGit (pinned in build/components.json) into the data root on first use. All git subprocess call sites (data_pipeline, memory/evolution, code_agent) now go through it, and the opencode child env gets the managed git on PATH. Also applies the full-project analysis findings: - engraphis_integration: missing `import re` made _strip_markdown raise NameError, silently skipping every wiki-content ingest into memory. - anthropic_client: the error generator closed over the unbound except variable `e`, yielding NameError instead of the API error message. - bedrock_client: get_event_loop() -> get_running_loop(). - docker_tools: semgrep CWE ids were computed but never surfaced; now appended to the finding description. - ruff cleanup across api/ (unused imports, import order, dead locals, ambiguous `l` names, semicolon statements). - i18n: all 9 non-English locales completed against en.json (14-44 missing keys each) with a vitest key-sync test to keep them complete.
1 parent 272f593 commit 6cd2c72

66 files changed

Lines changed: 1082 additions & 297 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/agent_loop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
from typing import Any, AsyncIterator, Awaitable, Callable, Optional
2727

2828
from api.anthropic_client import AnthropicClient
29-
from api.openai_client import OpenAIClient
3029
from api.litellm_client import LiteLLMClient
30+
from api.openai_client import OpenAIClient
3131
from api.provider_streaming import stream_provider_response
3232
from api.search_tool import ToolHandler, native_tool_name_to_prefix
3333
from api.stream_events import SendProcess, ThinkingSink, encode_process

api/anthropic_client.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
Anthropic requires for OAuth-authenticated requests.
1313
"""
1414

15-
from typing import Any, AsyncIterator, Dict
1615
import json
1716
import logging
1817
import os
18+
from typing import Any, AsyncIterator, Dict
1919

2020
import aiohttp
2121
import requests
22-
2322
from adalflow.core.model_client import ModelClient
2423
from adalflow.core.types import ModelType
2524

@@ -284,9 +283,12 @@ async def error_response_generator():
284283
data = await response.json()
285284
except Exception as e:
286285
log.error(f"Error calling Anthropic API: {str(e)}")
286+
# Bind the message now: Python unbinds `e` when the except block
287+
# ends, and the generator runs after that (NameError otherwise).
288+
error_message = f"Error calling Anthropic API: {str(e)}"
287289

288290
async def exception_generator():
289-
yield f"Error calling Anthropic API: {str(e)}"
291+
yield error_message
290292
return exception_generator()
291293

292294
text = self._extract_text(data)

api/azureai_client.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
"""AzureOpenAI ModelClient integration."""
22

3+
import logging
34
import os
5+
import re
6+
import sys
47
from typing import (
5-
Dict,
6-
Sequence,
7-
Optional,
8-
List,
98
Any,
10-
TypeVar,
119
Callable,
10+
Dict,
1211
Generator,
13-
Union,
12+
List,
1413
Literal,
14+
Optional,
15+
Sequence,
16+
TypeVar,
17+
Union,
1518
)
16-
import re
1719

18-
import logging
1920
import backoff
2021

2122
# optional import
22-
from adalflow.utils.lazy_import import safe_import, OptionalPackages
23-
24-
import sys
23+
from adalflow.utils.lazy_import import OptionalPackages, safe_import
2524

2625
openai = safe_import(OptionalPackages.OPENAI.value[0], OptionalPackages.OPENAI.value[1])
2726
# Importing all Azure packages together
@@ -35,32 +34,33 @@
3534
sys.modules[name] = module
3635

3736
# Use the modules as if they were imported normally
37+
from adalflow.components.model_client.utils import parse_embedding_response
38+
from adalflow.core.model_client import ModelClient
39+
from adalflow.core.types import (
40+
CompletionUsage,
41+
EmbedderOutput,
42+
GeneratorOutput,
43+
ModelType,
44+
TokenLogProb,
45+
)
3846
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
3947

4048
# from azure.core.credentials import AccessToken
41-
from openai import AzureOpenAI, AsyncAzureOpenAI, Stream
4249
from openai import (
4350
APITimeoutError,
51+
AsyncAzureOpenAI,
52+
AzureOpenAI,
53+
BadRequestError,
4454
InternalServerError,
4555
RateLimitError,
56+
Stream,
4657
UnprocessableEntityError,
47-
BadRequestError,
4858
)
4959
from openai.types import (
5060
Completion,
5161
CreateEmbeddingResponse,
5262
)
53-
from openai.types.chat import ChatCompletionChunk, ChatCompletion
54-
55-
from adalflow.core.model_client import ModelClient
56-
from adalflow.core.types import (
57-
ModelType,
58-
EmbedderOutput,
59-
TokenLogProb,
60-
CompletionUsage,
61-
GeneratorOutput,
62-
)
63-
from adalflow.components.model_client.utils import parse_embedding_response
63+
from openai.types.chat import ChatCompletion, ChatCompletionChunk
6464

6565
log = logging.getLogger(__name__)
6666
T = TypeVar("T")

api/bedrock_client.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
"""AWS Bedrock ModelClient integration."""
22

33
import asyncio
4-
import os
54
import json
65
import logging
76
import threading
7+
from typing import Any, AsyncGenerator, Dict, List, Optional, Sequence
8+
9+
import backoff
810
import boto3
911
import botocore
10-
import backoff
11-
from typing import Dict, Any, Optional, List, Generator, Union, AsyncGenerator, Sequence
12-
1312
from adalflow.core.model_client import ModelClient
14-
from adalflow.core.types import ModelType, GeneratorOutput, EmbedderOutput
13+
from adalflow.core.types import EmbedderOutput, ModelType
1514

1615
# Configure logging
1716
from api.logging_config import setup_logging
@@ -59,10 +58,10 @@ def __init__(
5958
super().__init__(*args, **kwargs)
6059
from api.config import (
6160
AWS_ACCESS_KEY_ID,
62-
AWS_SECRET_ACCESS_KEY,
63-
AWS_SESSION_TOKEN,
6461
AWS_REGION,
6562
AWS_ROLE_ARN,
63+
AWS_SECRET_ACCESS_KEY,
64+
AWS_SESSION_TOKEN,
6665
)
6766

6867
self.aws_access_key_id = aws_access_key_id or AWS_ACCESS_KEY_ID
@@ -512,7 +511,7 @@ async def astream(self, api_kwargs: Dict = None, model_type: ModelType = None) -
512511
request_body["topP"] = api_kwargs["top_p"]
513512

514513
body = json.dumps(request_body)
515-
loop = asyncio.get_event_loop()
514+
loop = asyncio.get_running_loop()
516515
queue: "asyncio.Queue[Any]" = asyncio.Queue()
517516
_DONE = object()
518517

api/cache_eviction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
from typing import Optional
3939

4040
from api.wiki_cache_paths import (
41+
LEGACY_WIKI_CACHE_FILE_PREFIX,
4142
WIKI_CACHE_DIR,
4243
WIKI_CACHE_FILE_PREFIX,
43-
LEGACY_WIKI_CACHE_FILE_PREFIX,
4444
)
4545

4646
logger = logging.getLogger(__name__)

api/code_agent/binary.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
a killed download never leaves a half-written binary behind.
1919
"""
2020

21-
import logging
2221
import hashlib
22+
import logging
2323
import os
2424
import platform
2525
import re
@@ -35,8 +35,8 @@
3535
from contextlib import contextmanager
3636
from typing import Awaitable, Callable, Optional
3737

38-
from api.data_root import get_data_root
3938
from api.component_manifest import component_manifest
39+
from api.data_root import get_data_root
4040

4141
logger = logging.getLogger(__name__)
4242

api/code_agent/config.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
* disables sharing and self-update (the app manages the binary).
1818
"""
1919

20-
import json
2120
import hashlib
2221
import hmac
22+
import json
2323
import logging
2424
import os
2525
import secrets
26+
import shutil
2627
import sys
2728
from typing import Optional, Tuple
2829

@@ -302,6 +303,16 @@ def build_child_env(config_path: str, extra_env: dict, server_password: str) ->
302303
env["XDG_CONFIG_HOME"] = os.path.join(home, "config")
303304
env["XDG_CACHE_HOME"] = os.path.join(home, "cache")
304305
env["XDG_STATE_HOME"] = os.path.join(home, "state")
306+
# opencode shells out to git itself; when git only exists as our managed
307+
# portable MinGit (Windows machines without Git installed), the child
308+
# needs its cmd/ dir on PATH to find it.
309+
try:
310+
from api.git_exe import resolve_git
311+
resolved = resolve_git()
312+
if resolved and not shutil.which("git", path=env.get("PATH")):
313+
env["PATH"] = os.path.dirname(resolved) + os.pathsep + env.get("PATH", "")
314+
except Exception: # noqa: BLE001 - a git-less env is still usable for editing
315+
pass
305316
return env
306317

307318

api/code_agent/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def _maybe_backfill_wiki_commit(cached, owner: str, repo: str, repo_type: str,
5757
return None
5858
try:
5959
import json
60-
import os
60+
6161
from api.wiki_cache_paths import list_cache_files
6262

6363
files = list_cache_files(repo_type, owner, repo, language)

api/code_agent/manager.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@
3939

4040
logger = logging.getLogger(__name__)
4141

42+
43+
def _git() -> str:
44+
"""Resolved git binary; falls back to bare "git" so these best-effort
45+
helpers keep their None-on-failure contract instead of raising."""
46+
try:
47+
from api.git_exe import git_executable
48+
return git_executable()
49+
except Exception:
50+
return "git"
51+
52+
4253
IDLE_SHUTDOWN_SECONDS = 30 * 60
4354
HEALTH_TIMEOUT_SECONDS = 30
4455
# Kept small: only used for error surfacing when the process dies.
@@ -119,7 +130,7 @@ def repo_head_commit(repo_dir: str) -> Optional[str]:
119130
"""HEAD commit of a clone, or None for non-git dirs / errors."""
120131
try:
121132
out = subprocess.run(
122-
["git", "rev-parse", "HEAD"],
133+
[_git(), "rev-parse", "HEAD"],
123134
cwd=repo_dir, capture_output=True, text=True, timeout=10,
124135
)
125136
if out.returncode == 0:
@@ -138,13 +149,13 @@ def repo_worktree_fingerprint(repo_dir: str) -> Optional[str]:
138149
"""
139150
try:
140151
head = subprocess.run(
141-
["git", "rev-parse", "HEAD"],
152+
[_git(), "rev-parse", "HEAD"],
142153
cwd=repo_dir,
143154
capture_output=True,
144155
timeout=10,
145156
)
146157
status = subprocess.run(
147-
["git", "status", "--porcelain=v1", "-z", "--untracked-files=all"],
158+
[_git(), "status", "--porcelain=v1", "-z", "--untracked-files=all"],
148159
cwd=repo_dir,
149160
capture_output=True,
150161
timeout=20,
@@ -162,14 +173,14 @@ def _maybe_unshallow(repo_dir: str) -> None:
162173
just leave the clone shallow (editing still works)."""
163174
try:
164175
probe = subprocess.run(
165-
["git", "rev-parse", "--is-shallow-repository"],
176+
[_git(), "rev-parse", "--is-shallow-repository"],
166177
cwd=repo_dir, capture_output=True, text=True, timeout=10,
167178
)
168179
if probe.returncode != 0 or probe.stdout.strip() != "true":
169180
return
170181
logger.info("Unshallowing clone at %s for code editing...", repo_dir)
171182
subprocess.run(
172-
["git", "fetch", "--unshallow", "--tags"],
183+
[_git(), "fetch", "--unshallow", "--tags"],
173184
cwd=repo_dir, capture_output=True, text=True, timeout=120,
174185
)
175186
except (subprocess.TimeoutExpired, OSError) as e:

api/code_agent/routes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,29 @@
3636
WebSocketDisconnect,
3737
)
3838

39+
from api.chat_common import capture_chat_exchange
3940
from api.code_agent import events as oc_events
4041
from api.code_agent.binary import (
4142
OPENCODE_VERSION,
4243
download_opencode,
4344
installed_opencode_version,
4445
resolve_opencode_binary,
4546
)
47+
from api.code_agent.config import describe_target, map_provider
4648
from api.code_agent.context import build_code_session_context
4749
from api.code_agent.manager import (
4850
CodeAgentError,
4951
repo_key_for,
5052
repo_worktree_fingerprint,
5153
)
52-
from api.code_agent.service import code_agent as manager
5354
from api.code_agent.models import (
5455
CodeAbortRequest,
5556
CodeAgentUpdateRequest,
5657
CodeChatRequest,
5758
CodeSessionRequest,
5859
CodeSessionResponse,
5960
)
60-
from api.code_agent.config import describe_target, map_provider
61-
from api.chat_common import capture_chat_exchange
61+
from api.code_agent.service import code_agent as manager
6262
from api.security import (
6363
authorization_is_valid,
6464
authorize_websocket,

0 commit comments

Comments
 (0)