Files
orca/src/shared/windows-command-line-budget.ts
Neil ef0d5931bc fix(source-control): budget WSL bulk git command lines by bytes, not path count (#16634)
Selecting ~100 changed files in a WSL worktree and hitting Stage All did
nothing: the files stayed unstaged and the operation reported a failure.
Bulk stage/unstage/discard chunked pathspecs 100 at a time, a count picked
against a raw argv. A WSL-routed write is not a raw argv -- it is folded
into one login-shell command line that shell-quotes every pathspec, quotes
the result again, and embeds it three times (one branch per guest shell),
so the finished line runs ~3.4x the raw pathspec bytes. Realistic project
paths blew past the 32767-character CreateProcess cap at 100 paths and
wsl.exe refused to spawn, with nothing staged.

Chunking now measures the finished command line through the real resolver,
so the wrapper's quoting rules live in one place and native, WSL and SSH
hosts each get the budget of the host that actually spawns. A pathspec too
long to fit alone still ships alone rather than being dropped, and no chunk
is ever emitted empty -- a pathspec-free `clean -ffdx` would have swept the
whole worktree.

The tracked-path listing behind that discard also fences the WSL login
shell now. Its stdout was parsed NUL-delimited without a fence, so Ubuntu's
interactive rc banner glued itself onto the first record: that path failed
to match anything git reported and was treated as untracked, sending a
tracked file to `git clean` instead of `git restore`. Not observing a path
in ls-files output is not evidence the path is untracked.

The Windows command-line cap and its libuv-aware length estimate move out
of the WSL runner into src/shared/windows-command-line-budget.ts, shared by
both callers.
2026-08-26 14:37:15 -07:00

29 lines
1.4 KiB
TypeScript

/**
* How long a command line may get before `CreateProcess` refuses it.
*
* Windows caps a command line at 32767 characters, and *everything* shares that
* one budget: the binary, `wsl.exe -d <distro> --exec`, the login-shell wrapper
* and the payload. So the number only means anything when it is measured on the
* FINISHED line. Two shipped defects came from measuring a part instead: a
* script-only threshold in the WSL runner, where a multi-KB login PATH pushed a
* legal-looking script over the real limit, and count-only chunking of bulk git
* pathspecs, where the login-shell wrapper tripled the line behind our back.
*
* The 2767-character margin absorbs what we do not model exactly (the distro
* name, libuv's requoting of the outer argv).
*/
export const MAX_COMMAND_LINE_CHARS = 30_000
/**
* What `CreateProcess` will count.
*
* libuv escapes every `"` and doubles a backslash run before a quote, so a
* quote-dense script costs more than its length. Charging one extra character
* per `"` or `\\` keeps the estimate on the safe side of the cap; an earlier
* version claimed to over-count and in fact under-counted, which put a
* quote-heavy ~26KB script on argv and over the real limit.
*/
export function commandLineLength(args: readonly string[]): number {
return args.reduce((total, arg) => total + arg.length + 3 + (arg.match(/["\\]/g)?.length ?? 0), 0)
}