Found while diagnosing #4624.
Problem
In an untrusted project, every workspace.executeBash call first runs gitNoRepoAutomationEnvForLocalRepo (src/node/services/tools/bash.ts ~L920, src/node/utils/gitNoHooksEnv.ts). That function runs four git processes one after another from the Electron main process: rev-parse --git-dir, config --local --bool extensions.worktreeConfig, the local include check, and config --includes --get-regexp.
Opening a workspace fires four executeBash calls for that workspace (GitStatusStore status script, git fetch, PRStatusStore gh pr view, gh stack view). In an untrusted project that is 16 discovery forks plus 4 bash forks, instead of 4.
Each spawn blocks the main thread synchronously while the large Electron process forks. Measured with a temporary ChildProcess.prototype.spawn wrapper in perf.chatSwitch.spec.ts (host load ~30 on 32 cores): median 12.5 ms per spawn, 196 spawns / 2.4 s blocked in one run, 150 of them discovery. (The V8 CPU profile under-reports this, because signals are blocked during fork.) Those forks land inside the onChat replay's history read.
Effect on cold open (same build, perf fixture untrusted vs trusted, alternating runs, medians of 9 switches each):
| metric |
untrusted |
trusted |
| cold-open-small server.totalMs |
340.6 |
151.9 |
| cold-open-small historyLockWaitMs |
58.1 |
2.3 |
| cold-open-small first row (store switch) |
493.1 |
388.3 |
Options
- Run discovery in one spawn: a single
sh -c script, like gitNoRepoAutomationEnvForRuntimeRepo, so 4 forks become 1 per call. No staleness, but it rewrites the fail-closed parsing. On Windows it needs Git Bash, which LocalBaseRuntime already requires for bash.
- Share one in-flight discovery per repo path between concurrent callers, so 16 forks become 4 per open. A caller then gets a config snapshot that started before it arrived. Discovery is already a snapshot taken before the script runs, but this is a security control, so review will focus on it.
- Reduce the fork cost itself (a spawn helper process), which would also help trusted projects (see the companion issue).
Prefer option 1, in its own PR. Measure with perf.chatSwitch.spec.ts after temporarily removing its trustDemoProject call.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Found while diagnosing #4624.
Problem
In an untrusted project, every
workspace.executeBashcall first runsgitNoRepoAutomationEnvForLocalRepo(src/node/services/tools/bash.ts~L920,src/node/utils/gitNoHooksEnv.ts). That function runs fourgitprocesses one after another from the Electron main process:rev-parse --git-dir,config --local --bool extensions.worktreeConfig, the local include check, andconfig --includes --get-regexp.Opening a workspace fires four
executeBashcalls for that workspace (GitStatusStore status script, git fetch, PRStatusStoregh pr view,gh stack view). In an untrusted project that is 16 discovery forks plus 4 bash forks, instead of 4.Each
spawnblocks the main thread synchronously while the large Electron process forks. Measured with a temporaryChildProcess.prototype.spawnwrapper inperf.chatSwitch.spec.ts(host load ~30 on 32 cores): median 12.5 ms per spawn, 196 spawns / 2.4 s blocked in one run, 150 of them discovery. (The V8 CPU profile under-reports this, because signals are blocked during fork.) Those forks land inside the onChat replay's history read.Effect on cold open (same build, perf fixture untrusted vs trusted, alternating runs, medians of 9 switches each):
Options
sh -cscript, likegitNoRepoAutomationEnvForRuntimeRepo, so 4 forks become 1 per call. No staleness, but it rewrites the fail-closed parsing. On Windows it needs Git Bash, whichLocalBaseRuntimealready requires for bash.Prefer option 1, in its own PR. Measure with
perf.chatSwitch.spec.tsafter temporarily removing itstrustDemoProjectcall.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high