Skip to content

[Bug]: Spawning git forks the whole server process, taking a median 8 to 32 ms from fork to exec under agent load #31422

Open

Description

@anaclumos

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

  1. Run the server on Linux with many agent threads working in git worktree projects at once (here 7 to 156 runs working).
  2. 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.
  3. Sample the server's main thread with perf record -g -t <main thread id> for 20 seconds.
  4. 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. _Fork from node::ProcessWrap::Spawn was on the stack in 33% of main-thread CPU samples, with copy_page_range as the leaf. The main thread slept in pipe_read in 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_range in 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 git included ThreadPullRequestServiceV2.synchronize calling GitManager.branchPullRequest, which the server enqueues on checkpoint.captured and on terminal run.updated events, and 37% of git calls were GitVcsDriver.readConfigValue.
  • On the same machine, fork plus exec of /bin/true from 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 (AnonHugePages 1.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

  1. added a commit that references this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions