Skip to content

Commit 9c34e6f

Browse files
committed
fix: unblock event loop for analyze_repo, detach git subprocess stdin on Windows
- Run analyze_repo via asyncio.to_thread under an asyncio.Lock: keeps tree-sitter usage serialized without blocking the MCP stdio pump (aligns with the to_thread pattern used by all other sync handlers) - Add stdin=DEVNULL to GitIgnoreFilter's git calls so children never inherit the MCP JSON-RPC stdin pipe, which hangs on Windows ProactorEventLoop
1 parent 0b9bebe commit 9c34e6f

2 files changed

Lines changed: 9 additions & 4 deletions

File tree

‎codewiki/mcp/server.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ async def list_tools() -> list[Tool]:
372372
return _fine_grained_tools() + _legacy_tools()
373373

374374

375+
_analyze_lock = asyncio.Lock()
376+
375377
@server.call_tool()
376378
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
377379
"""Route tool calls to the appropriate handler."""
@@ -382,10 +384,11 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
382384
if name == "analyze_repo":
383385
from codewiki.mcp.tools.analysis import handle_analyze_repo
384386

385-
# NOTE: Tree-sitter C extensions are not thread-safe, so this
386-
# must run on the main thread (blocking the event loop is
387-
# acceptable for this one-time heavy operation).
388-
return [_text(handle_analyze_repo(arguments, _store))]
387+
# NOTE: Tree-sitter C extensions are not thread-safe, so we use
388+
# a lock to ensure only one analyze_repo runs at a time, but
389+
# offload to a thread to avoid blocking the stdio pump.
390+
async with _analyze_lock:
391+
return [_text(await asyncio.to_thread(handle_analyze_repo, arguments, _store))]
389392

390393
elif name == "read_code_components":
391394
from codewiki.mcp.tools.code_reader import handle_read_code_components

‎codewiki/src/be/dependency_analyzer/analysis/repo_analyzer.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def _load_git_ignored_paths(self) -> bool:
5454
capture_output=True,
5555
text=True,
5656
timeout=10,
57+
stdin=subprocess.DEVNULL,
5758
)
5859
if root_result.returncode != 0:
5960
logger.debug(
@@ -82,6 +83,7 @@ def _load_git_ignored_paths(self) -> bool:
8283
check=True,
8384
capture_output=True,
8485
timeout=30,
86+
stdin=subprocess.DEVNULL,
8587
)
8688
except (OSError, ValueError, subprocess.SubprocessError) as exc:
8789
logger.warning(

0 commit comments

Comments
 (0)