Summary
On Windows, shell-language ctx_execute/ctx_batch_execute calls run their command in a Git Bash (MSYS) process whose PATH is stripped down to /usr/bin:/bin — it does not carry the parent/inherited PATH. This breaks resolution of any Windows-installed CLI that isn't already inside MSYS's own /usr/bin (e.g. GitHub CLI gh, and by extension any git credential helper configured to shell out to gh auth git-credential, which additionally fails because the sandboxed process also has no /dev/tty).
Root cause
build/executor.js:
/** Pure helper — exported for unit testing. Restores parent PATH after shell startup. */
export function buildShellScriptContent(code, inheritedPath, platform) {
if (platform === "win32" || !inheritedPath)
return code;
return `export PATH=${quoteForPosixShell(inheritedPath)}\n${code}`;
}
and its call site:
writeFileSync(fp, buildShellScriptContent(shellCode, process.env.PATH, process.platform), { encoding: "utf-8", mode: 0o700 });
This function explicitly restores the parent-inherited PATH into the generated script only on non-Windows (if (platform === "win32" ...) return code — the win32 branch is a no-op passthrough). #buildSafeEnv() does correctly propagate the real process.env.PATH into the child process's env, and does prepend Git's own usr/bin/bin, but the spawned Git Bash process does not appear to actually use that inherited PATH for command resolution inside the script — it starts with MSYS's own minimal default (/usr/bin:/bin), same shape as a fresh login shell that hasn't sourced /etc/profile. Because the Windows branch of buildShellScriptContent never injects an export PATH=... line the way the POSIX branch does, there is nothing to override that MSYS default inside the script itself.
Reproduction
ctx_batch_execute(commands: [
{ label: "shell identity", command: "echo PATH=$PATH" },
{ label: "gh direct", command: "'/c/Program Files/GitHub CLI/gh' --version" },
{ label: "gh via PATH", command: "which gh; gh --version" }
])
Result:462
PATH=/usr/bin:/bin
gh version 2.101.0 ... # works via absolute path
which: no gh in (/usr/bin:/bin)
... gh: command not found
Yet in a normal (non-sandboxed) Git Bash / the host's own Bash tool, gh resolves fine because the real Windows PATH (which includes C:\Program Files\GitHub CLI) is on it.
Secondary, compounding failure: any git command that needs to shell out to a credential helper (e.g. credential.https://github.com.helper=!gh auth git-credential) additionally fails inside the sandbox with:
error: failed to execute prompt script (exit code 1)
fatal: could not read Password for '...': No such file or directory
because the sandbox process also has no /dev/tty for an interactive credential prompt fallback — so even once gh is resolvable, a bare git pull/push that needs credential-helper mediation still cannot succeed non-interactively inside the sandbox the way it does in a normal shell session.
Impact
- Every Windows user relying on
gh (or any other Windows-installed, non-MSYS CLI) from inside ctx_execute/ctx_batch_execute shell scripts hits a misleading command not found, despite the same binary resolving fine via which/direct invocation in a normal shell, and despite #buildSafeEnv() clearly intending to pass the real PATH through.
- This is silently inconsistent with the POSIX behavior, where the same class of tool (anything installed outside the sandboxed shell's own minimal
PATH) resolves correctly because buildShellScriptContent restores it.
Suggested fix
Make the Windows branch of buildShellScriptContent do the POSIX-equivalent restoration — i.e. emit a PATH="<inheritedPath>" (or Windows-appropriate export PATH=... after MSYS path conversion) line at the top of the generated script instead of returning code unchanged, so Git Bash's own default MSYS PATH doesn't shadow the inherited environment. At minimum, document the current behavior (and the /dev/tty absence for git credential-helper prompts) as a known limitation, since right now it presents as a plain "command not found"/"could not read Password" with no pointer to the real cause.
Summary
On Windows, shell-language
ctx_execute/ctx_batch_executecalls run their command in a Git Bash (MSYS) process whosePATHis stripped down to/usr/bin:/bin— it does not carry the parent/inheritedPATH. This breaks resolution of any Windows-installed CLI that isn't already inside MSYS's own/usr/bin(e.g. GitHub CLIgh, and by extension anygitcredential helper configured to shell out togh auth git-credential, which additionally fails because the sandboxed process also has no/dev/tty).Root cause
build/executor.js:and its call site:
This function explicitly restores the parent-inherited
PATHinto the generated script only on non-Windows (if (platform === "win32" ...) return code— the win32 branch is a no-op passthrough).#buildSafeEnv()does correctly propagate the realprocess.env.PATHinto the child process'senv, and does prepend Git's ownusr/bin/bin, but the spawned Git Bash process does not appear to actually use that inheritedPATHfor command resolution inside the script — it starts with MSYS's own minimal default (/usr/bin:/bin), same shape as a fresh login shell that hasn't sourced/etc/profile. Because the Windows branch ofbuildShellScriptContentnever injects anexport PATH=...line the way the POSIX branch does, there is nothing to override that MSYS default inside the script itself.Reproduction
Result:462
Yet in a normal (non-sandboxed) Git Bash / the host's own Bash tool,
ghresolves fine because the real WindowsPATH(which includesC:\Program Files\GitHub CLI) is on it.Secondary, compounding failure: any
gitcommand that needs to shell out to a credential helper (e.g.credential.https://github.com.helper=!gh auth git-credential) additionally fails inside the sandbox with:because the sandbox process also has no
/dev/ttyfor an interactive credential prompt fallback — so even onceghis resolvable, a baregit pull/pushthat needs credential-helper mediation still cannot succeed non-interactively inside the sandbox the way it does in a normal shell session.Impact
gh(or any other Windows-installed, non-MSYS CLI) from insidectx_execute/ctx_batch_executeshell scripts hits a misleadingcommand not found, despite the same binary resolving fine viawhich/direct invocation in a normal shell, and despite#buildSafeEnv()clearly intending to pass the realPATHthrough.PATH) resolves correctly becausebuildShellScriptContentrestores it.Suggested fix
Make the Windows branch of
buildShellScriptContentdo the POSIX-equivalent restoration — i.e. emit aPATH="<inheritedPath>"(or Windows-appropriateexport PATH=...after MSYS path conversion) line at the top of the generated script instead of returningcodeunchanged, so Git Bash's own default MSYSPATHdoesn't shadow the inherited environment. At minimum, document the current behavior (and the /dev/tty absence for git credential-helper prompts) as a known limitation, since right now it presents as a plain "command not found"/"could not read Password" with no pointer to the real cause.