Windows: web UI hangs forever — list_projects deadlocks the HTTP server (git subprocess via _popen)
Summary
On Windows, with the graph UI enabled (--ui=true), the web UI never loads: the
page shell is served but the app stays blank. The cause is that the MCP tool
list_projects hangs forever and, because the UI HTTP server is
single-threaded, wedges the entire server — no further request is answered.
- Affected: Windows, UI mode only (
--ui=true / the web graph). Reproduced
with the production binary built from main (cbm-with-ui).
- Not affected: the plain MCP-stdio server (no UI) and the CLI —
list_projects
returns instantly there. That is why normal MCP usage never showed the problem.
- Requires: git-for-Windows (the MSYS2/Cygwin build) on
PATH, which is the
standard Windows git.
Root cause (confirmed with a debugger)
list_projects resolves git context per project:
add_git_context_json → cbm_git_context_resolve → git_capture → cbm_popen
(src/git/git_context.c). On Windows cbm_popen is the CRT _popen, which spawns
the child with CreateProcess(bInheritHandles = TRUE) and therefore leaks every
inheritable handle the server holds into the git child — including the
Winsock/AFD helper handles that only exist once the HTTP server has called
WSAStartup/socket().
git-for-Windows (MSYS2/Cygwin runtime) classifies every inherited handle on
startup by calling NtQueryObject on it. NtQueryObject deadlocks on an
inherited socket/AFD handle. So git never reaches rev-parse, never writes output,
and the server thread blocks forever in fgets on git's stdout pipe.
Three-thread deadlock captured with gdb on the hung process:
# UI request thread — waiting for git output that never comes
ntdll!ZwReadFile -> ReadFile -> msvcrt!fgets
-> git_capture() (git_context.c)
-> cbm_git_context_resolve -> add_git_context_json -> handle_list_projects
-> cbm_mcp_server_handle -> cbm_http_server_run -> http_thread
# the git.exe child — stuck classifying an inherited handle
ntdll!ZwQueryObject (NtQueryObject)
# server main thread — the blocking read on the MCP stdin pipe git inherited/queries
ntdll!ZwReadFile -> ... -> cbm_getline -> cbm_mcp_server_run
Why UI-only: the plain stdio server and the CLI have no listening/AFD socket
handles, so git inherits nothing that trips NtQueryObject.
Reproduction
- On Windows, build with the UI:
make -f Makefile.cbm cbm-with-ui.
- Index any two git repos so
list_projects returns >1 project.
- Run
codebase-memory-mcp --ui=true --port=9749 and open the UI (or just
POST /rpc {"method":"tools/call","params":{"name":"list_projects"}}).
- The request never returns; the whole UI server stops responding.
tasklist
shows a cmd /c git ... rev-parse child that never exits.
Fix
Spawn git so it inherits only the stdout pipe. See the PR: cbm_popen on
Windows is reimplemented with CreateProcess + STARTUPINFOEX and an explicit
PROC_THREAD_ATTRIBUTE_HANDLE_LIST (stdout write-end + a NUL handle for
stdin/stderr). Nothing else crosses into git → no foreign handle to deadlock on.
The POSIX path is unchanged (popen already sets O_CLOEXEC).
Windows: web UI hangs forever —
list_projectsdeadlocks the HTTP server (git subprocess via_popen)Summary
On Windows, with the graph UI enabled (
--ui=true), the web UI never loads: thepage shell is served but the app stays blank. The cause is that the MCP tool
list_projectshangs forever and, because the UI HTTP server issingle-threaded, wedges the entire server — no further request is answered.
--ui=true/ the web graph). Reproducedwith the production binary built from
main(cbm-with-ui).list_projectsreturns instantly there. That is why normal MCP usage never showed the problem.
PATH, which is thestandard Windows git.
Root cause (confirmed with a debugger)
list_projectsresolves git context per project:add_git_context_json→cbm_git_context_resolve→git_capture→cbm_popen(
src/git/git_context.c). On Windowscbm_popenis the CRT_popen, which spawnsthe child with
CreateProcess(bInheritHandles = TRUE)and therefore leaks everyinheritable handle the server holds into the git child — including the
Winsock/AFD helper handles that only exist once the HTTP server has called
WSAStartup/socket().git-for-Windows (MSYS2/Cygwin runtime) classifies every inherited handle on
startup by calling
NtQueryObjecton it.NtQueryObjectdeadlocks on aninherited socket/AFD handle. So git never reaches
rev-parse, never writes output,and the server thread blocks forever in
fgetson git's stdout pipe.Three-thread deadlock captured with gdb on the hung process:
Why UI-only: the plain stdio server and the CLI have no listening/AFD socket
handles, so git inherits nothing that trips
NtQueryObject.Reproduction
make -f Makefile.cbm cbm-with-ui.list_projectsreturns >1 project.codebase-memory-mcp --ui=true --port=9749and open the UI (or justPOST /rpc {"method":"tools/call","params":{"name":"list_projects"}}).tasklistshows a
cmd /c git ... rev-parsechild that never exits.Fix
Spawn git so it inherits only the stdout pipe. See the PR:
cbm_popenonWindows is reimplemented with
CreateProcess+STARTUPINFOEXand an explicitPROC_THREAD_ATTRIBUTE_HANDLE_LIST(stdout write-end + aNULhandle forstdin/stderr). Nothing else crosses into git → no foreign handle to deadlock on.
The POSIX path is unchanged (
popenalready setsO_CLOEXEC).