Skip to content

fix(win): prevent UI hang — spawn git without leaking handles into the child#799

Open
Flipper1994 wants to merge 1 commit into
DeusData:mainfrom
Flipper1994:fix/windows-ui-popen-handle-inheritance
Open

fix(win): prevent UI hang — spawn git without leaking handles into the child#799
Flipper1994 wants to merge 1 commit into
DeusData:mainfrom
Flipper1994:fix/windows-ui-popen-handle-inheritance

Conversation

@Flipper1994

Copy link
Copy Markdown

What does this PR do?

Fixes a Windows-only deadlock that makes the web UI hang forever. With --ui=true,
the MCP tool list_projects never returns and, because the UI HTTP server is
single-threaded, the whole server stops responding — the UI page loads but stays
blank. Closes #798.

Root cause

list_projects shells out to git per project for git context
(git_capturecbm_popen, src/git/git_context.c). On Windows cbm_popen was
the CRT _popen, which does CreateProcess(bInheritHandles = TRUE) and leaks
every inheritable handle into the git child — including the Winsock/AFD handles
that exist only in UI mode. git-for-Windows (MSYS2/Cygwin) classifies each inherited
handle via NtQueryObject on startup, which deadlocks on an inherited
socket/AFD handle. git never runs, so the server blocks forever in fgets on git's
stdout pipe. Confirmed with gdb: request thread in fgets/git_capture, the
git.exe child in ntdll!ZwQueryObject.

The plain MCP-stdio server and the CLI are unaffected (no socket handles to inherit),
which is why only the UI hangs.

The fix

Reimplement cbm_popen on Windows (src/foundation/compat_fs.c) to spawn via
CreateProcess + STARTUPINFOEX with an explicit
PROC_THREAD_ATTRIBUTE_HANDLE_LIST containing only the stdout write-end and a NUL
handle for the child's stdin/stderr. The git child now inherits only the pipe —
no sockets, no MCP stdin pipe, no Winsock handles — so there is no foreign handle for
NtQueryObject to deadlock on. cbm_pclose reaps the process via a small
FILE*HANDLE table. The POSIX path is unchanged (popen already opens its pipe
O_CLOEXEC). This is centralized in cbm_popen, so it also covers the watcher
(src/watcher/watcher.c) and git-history pass, which shell out to git the same way.

Validation (Windows, production cbm-with-ui binary)

before after
POST /rpc list_projects (UI) hang / timeout 200 in ~1 s, 6× stable
leftover git/cmd processes persist forever none
web UI in browser blank page renders, stays responsive

Checklist

  • Every commit is signed off (git commit -s)
  • Tests pass locally — note: this repo's test.sh builds with ASan/UBSan, which
    are unavailable on the MSYS2/MinGW toolchain used to reproduce this; the fix is
    #ifdef _WIN32-only and does not touch the POSIX path.
  • Lint passes
  • Regression test — the bug is Windows-UI-specific (needs a live HTTP server +
    git-for-Windows) and does not reproduce under the Linux CI; happy to add a
    Windows-only test under tests/windows/ if preferred.

On Windows with the UI enabled (--ui=true), list_projects hangs forever and
wedges the single-threaded HTTP server, so the web UI never loads.

Root cause: list_projects resolves git context per project via git_capture ->
cbm_popen (git_context.c). On Windows cbm_popen was _popen, which spawns the
child with CreateProcess(bInheritHandles=TRUE) and leaks every inheritable
handle into the git child -- including the Winsock/AFD handles that exist only
in UI mode. git-for-Windows (MSYS2/Cygwin) classifies each inherited handle via
NtQueryObject on startup, which deadlocks on an inherited socket/AFD handle, so
git never runs and the server blocks forever in fgets on git's stdout pipe.
Confirmed with gdb (request thread in fgets/git_capture, git.exe child in
ntdll!ZwQueryObject). The plain MCP-stdio server and the CLI are unaffected --
no socket handles to inherit -- which is why only the UI hangs.

Fix: reimplement cbm_popen on Windows with CreateProcess + STARTUPINFOEX and an
explicit PROC_THREAD_ATTRIBUTE_HANDLE_LIST containing only the stdout write-end
and a NUL handle for stdin/stderr. The git child now inherits only the pipe, so
there is no foreign handle for NtQueryObject to deadlock on. cbm_pclose reaps
via a small FILE*->HANDLE table. The POSIX path is unchanged (popen already
sets O_CLOEXEC). Centralized in cbm_popen, so it also covers watcher.c and the
git-history pass.

Signed-off-by: Flipper <jacobphilipp@ymail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Flipper1994 Flipper1994 requested a review from DeusData as a code owner July 3, 2026 14:07
@DeusData DeusData added bug Something isn't working windows Windows-specific issues ux/behavior Display bugs, docs, adoption UX stability/performance Server crashes, OOM, hangs, high CPU/memory priority/high Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker. labels Jul 4, 2026
@DeusData

DeusData commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Thanks for the Windows UI hang fix for #798. Triage: high-priority Windows/UI stability.

Review will focus on the custom Windows cbm_popen implementation: handle inheritance must be closed for git-for-Windows without leaking handles, hanging the HTTP server, or changing POSIX behavior. A regression that proves list_projects returns under UI mode on Windows is the important guard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority/high Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker. stability/performance Server crashes, OOM, hangs, high CPU/memory ux/behavior Display bugs, docs, adoption UX windows Windows-specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: web UI hangs — list_projects deadlocks the HTTP server (git via _popen handle inheritance)

2 participants