Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Run the server on Linux with many agent threads working in git worktree projects at once (here 7 to 156 runs working).
- Record process forks and execs for 15 to 20 seconds with
perf record -a -e sched:sched_process_fork -e sched:sched_process_exec, and pair each fork by the server's main thread with its child's exec. - Sample the server's main thread with
perf record -g -t <main thread id>for 20 seconds. - Time
GET /from loopback in a loop.
Expected behavior
Starting a git child does not hold up the event loop for a noticeable time, and status and pull request lookups do not starve HTTP, WebSocket, and MCP handling.
Actual behavior
- The server spawned 13 to 24 processes a second, 95 to 98% of them
git. - Measured from fork to the child's exec, a spawn took a median of 8 to 32 ms, and 18 to 26 ms in a 30-minute run after a restart.
- libuv forks the whole Node process.
_Forkfromnode::ProcessWrap::Spawnwas on the stack in 33% of main-thread CPU samples, withcopy_page_rangeas the leaf. The main thread slept inpipe_readin 77 of 300 state samples, which fits waiting on the exec pipe while the child releases its copy of the address space during exec (unmap_page_rangein 58% of the child's samples before exec). - Summed fork-to-exec intervals covered up to 44% of elapsed time in 15 to 20 second windows, and 35 to 41% across the 30-minute run.
- The median
GET /from loopback rose with the number of runs working: 93 ms with 7, 289 ms with 27, 762 ms with 86, and about 1 s with 124 to 156. - In the server traces, the top call chains for
gitincludedThreadPullRequestServiceV2.synchronizecallingGitManager.branchPullRequest, which the server enqueues oncheckpoint.capturedand on terminalrun.updatedevents, and 37% ofgitcalls wereGitVcsDriver.readConfigValue. - On the same machine, fork plus exec of
/bin/truefrom a test process holding 1.6 GB of touched private anonymous memory took a median of 199 ms in 4 KB pages and 4 ms in 2 MB pages (AnonHugePages1.68 GB). The server held 0.99 to 1.70 GB of resident memory, with 20 to 34 MB of page tables, during these measurements. - A server restart lowered the fork-to-exec median to 8 ms at first, but within 6 minutes the resumed runs raised the spawn rate and
GET /was back above 0.6 s.
Impact
With many agents running, the web UI and MCP tool calls stall; McpHttpServer.authenticateRequest spans reached 26 s.
Version or commit
0.1.8-nightly.20261104.4165
Environment
Linux 6.12.113 (Debian 13), Node v26.12.1 inside the t3 binary, 80-thread Xeon without AVX2, 512 GB of memory, a 32 GB state database.
Logs or stack traces
Main-thread CPU samples, 20 s at 499 Hz: 33% under _Fork from node::ProcessWrap::Spawn, with copy_page_range as the leaf. Child CPU samples between fork and exec: 58% unmap_page_range under unmap_vmas. Fork-to-exec intervals from sched_process_fork and sched_process_exec are a separate, elapsed-time measure.
Workaround
No lasting workaround found; a restart helped for minutes. #30838 and #24099 would cut the number of git calls. A proposed mitigation is running git from a small long-lived helper process, so each fork copies little memory.
Activity
test(server): reproduce #31422, ProcessRunner forks the server for ev…