pingdotgg / pingdotgg/t3code

shell.resolveSpawnCommand walks PATH × PATHEXT with statSync on every spawn and picks Git's cmd launcher

Open
#11,221 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

What happened

Every child process the server starts goes through resolveSpawnCommand, which re-scans PATH synchronously each time and, on Git for Windows, resolves git to the cmd\git.exe launcher. Each git command is therefore two processes, and the lookup itself burns event-loop time on every spawn.

Diagnosis
  • processRunner.ts:294 calls resolveSpawnCommand for every process. resolveSpawnExecutableWithNode (packages/shared/src/shell.ts:95-133) loops over every PATH entry times every candidate extension and calls NodeFS.statSync for each. Nothing is memoized, and the work is synchronous on the event loop.
  • On this machine that is ~22 ms per spawn. 2345 shell.resolveSpawnCommand spans in a 76-minute trace; ~580 s over 13 h.
  • Git for Windows puts Git\cmd on PATH ahead of Git\mingw64\bin, so the first hit is cmd\git.EXE, a launcher that execs the real mingw64\bin\git.exe. Every git command is two process creations (plus a conhost.exe each, see #2537).
Steps to reproduce
  1. Windows with Git for Windows installed the default way.
  2. Run the desktop app or t3 serve with a git project and let the background sweeps run (see #11220).
  3. Compare shell.resolveSpawnCommand span durations in server.trace.ndjson, and trace process creation: each runGitCommand shows git.exe (launcher) → git.exe (mingw64).

Expected: resolve a command once per (command, PATH, PATHEXT) and reuse it; resolve git to the real binary.
Actual: a full synchronous PATH scan per spawn, and a two-process git.

Version

0.0.41-nightly.20260908.1414 (desktop); code unchanged on main @ 211618fd9f.

Environment

Windows 11 Pro 26200, git 2.55.0.windows.5, gh 2.100.0.

Evidence
shell.resolveSpawnCommand    2345 spans / 76 min   (~22 ms each on this machine)
processRunner.runProcessCore 2345
Related issues

#2537 (per-process console cost), #7570 (stale shim resolution, closed). Neither covers the missing memoization or the launcher choice.

Fix applied or workaround

None. PATH order could be changed by hand, but the resolver would still scan on every spawn.

Filed by

Claude Code (Claude Fable 5.1), following the t3 triage playbook from a checkout of main.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read packages/shared/src/shell.ts:95-133 and processRunner.ts:294 to trace resolveSpawnCommand and its synchronous PATH/PATHEXT scan. Reproduce on Windows with Git for Windows and inspect shell.resolveSpawnCommand spans and process creation. Done means repeated resolutions reuse results for the same inputs and git resolves to the real binary rather than the cmd launcher.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
performance, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.