On Windows, each coding agent picks a shell for the model by itself, and none of them says which one or why. Windows is where their trackers hurt. On 18 October 2026, at about 07:05 IST, GitHub's search showed 5,253 open issues with "windows" in the title among the 23,221 open issues of openai/codex (171 say "powershell", 8 "git bash", 316 "wsl"); 894 of the 15,184 of anthropics/claude-code (69, 47 and 57); 95 of the 2,213 of github/copilot-cli (20, 2 and 15); and 7 of the 740 of google-gemini/gemini-cli, whose tracker is pruned (1, 0 and 1). Cursor has no public tracker, only a forum. The threads are about the shell. Codex CLI runs the model's commands in PowerShell, and the issue that asks for a setting to change that (#16579, 54 reactions) says so; the key it proposes, windows.shell_path, is not a key of the release tested here. Copilot CLI lists PowerShell 6 or newer as a prerequisite and a stock Windows has 5.1: #411 and #2355 are its PowerShell tool failing for want of pwsh.exe (the tests below found 1.0.98 falling back to Windows PowerShell 5.1). Gemini CLI runs its shell tool in PowerShell even under Git Bash (#12678, #21340) and fails in Constrained Language Mode (#12519). The launcher of the Cursor CLI hardcodes Windows PowerShell 5.1 and drops quotes (forum thread 168416). claude doctor and codex doctor check the install and the settings of one agent each, and neither says which shell the model gets or why.
agent-windows-doctor says it, for Claude Code, Codex CLI, Gemini CLI, GitHub Copilot CLI and Cursor together. It is a read-only command line tool: it lists every binary, launcher and shim of each agent with its version and which one a terminal runs first; says which shell each agent gives the model and how it chose it; says which shell runs each agent's hooks and whether your hook commands are bash lines for a PowerShell; reads the machine behind it (the PowerShell editions, the execution policy, Git for Windows, long paths, line endings, the code page, WSL, Node); and, with --live, proves the shell claims with the real binaries against a fake API on your own machine, at zero tokens. Every finding says what the agent does about it, the fix, and whether the claim is verified (the binary showed it in the tests), documented (the vendor's documentation or a maintainer's words) or not verified. On Linux and macOS it says there is nothing to check and exits 0.
check prints, in this order:
- The machine. The Windows version and build (and whether Codex's MXC sandbox can run on it), every PowerShell edition found with its language mode and its execution policy per scope, Git for Windows and every
bash.exeon PATH (Git Bash, the WSL launcher), long paths,core.autocrlf, the console code page, WSL distributions, and Node and npm with the Node each installed package asks for. - Every agent on the machine. Each binary, launcher or shim found, with its version (read from
package.json, the file's version resource or the version folder, and the output says which), how it was installed and which one runs first. - The shell each agent gives the model, and how it chose. The table below.
- Hooks on Windows. The hook files each agent reads, what starts each hook command (a shell, and which), and whether the command is a bash line that a PowerShell reads differently.
- Codex on Windows.
CODEX_HOME, the config layers that bear on the shell, the Windows sandbox it selects and why,windows.shell_path. - Findings. 77 ids of the form
<agent>.<name>, each with a severity (error, warning, note), what the agent does, the fix, and a basis mark. - With
--live: a proof against each real binary, "proved with the real codex 0.163.0".
| Agent | The shell the model gets on Windows | How it is chosen | What runs a hook |
|---|---|---|---|
| Claude Code 2.1.302 | The Bash tool (Git Bash) when Git Bash is found; the PowerShell tool when it is not, and when CLAUDE_CODE_USE_POWERSHELL_TOOL or your account says so |
Git Bash: CLAUDE_CODE_GIT_BASH_PATH, then C:\Program Files\Git and C:\Program Files (x86)\Git, then the first git.exe that where.exe lists. PowerShell: pwsh on PATH, then PowerShell 7 under ProgramW6432, then .dotnet\tools, then powershell |
Git Bash (bash.exe -c) when it was found, else powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command |
| Codex CLI 0.163.0 | PowerShell | In this order, each started by its full path: pwsh.exe on PATH; C:\Program Files\PowerShell\7\pwsh.exe (a path written into Codex, used whether or not PATH lists it); powershell.exe on PATH; C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe. No key chooses it: windows.shell_path is ignored |
PowerShell: -NoProfile -Command |
| Gemini CLI 0.64.0 | PowerShell | pwsh.exe when PATH has one, else powershell.exe, both by bare name through PATH; with neither, spawn powershell.exe ENOENT |
powershell.exe -NoProfile -NonInteractive -Command "<command>; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }" |
| GitHub Copilot CLI 1.0.98 | PowerShell (its powershell tool) |
It tries pwsh.exe, then powershell.exe, by bare name through PATH; with neither, "PowerShell is not available" |
The powershell field: powershell.exe -nop -nol -c; the bash field only through a bash that works |
| Cursor CLI | Not run here. Its launcher starts Windows PowerShell 5.1; the shell it gives the model is not stated (documented) | The launcher, read from disk | Documented from the forum: composed as PowerShell, run with bash when MSYSTEM is set |
| What | Tokens |
|---|---|
The command line tool: check, agents, explain, and check --live (a fake API answers; no model is reached) |
0 |
check --markdown, if you paste it into an agent yourself |
At most 12 findings of at most 500 characters each and a header of three lines: about 7,500 characters, under 2,000 tokens. The example below is 4,326 characters |
There is no plugin, no skill and no hook: nothing is put in front of a model unless you paste a report.
Node 18 or newer. No dependencies, nothing to build.
npx -y github:nrzz/agent-windows-doctor check --project .or
git clone https://github.com/nrzz/agent-windows-doctor
cd agent-windows-doctor
node bin/agent-windows-doctor.mjs check --project .The examples below call the command agent-windows-doctor.
Usage: agent-windows-doctor <command> [options]
Read (they change nothing):
check [--project <dir>] [--agent claude,codex,gemini,copilot,cursor|all] [--live] [--markdown] [--max <n>]
[--claude <path>] [--codex <path>] [--gemini <path>] [--copilot <path>]
on Windows, for Claude Code, Codex CLI, Gemini CLI, GitHub Copilot CLI and Cursor: the machine
(Windows, PowerShell editions with language mode and execution policy, Git for Windows, every
bash.exe on PATH, long paths, line endings, code page, WSL, Node); every program of each agent
with its version, where it came from and which one runs first; the shell each gives the model
and how it chose; what runs its hooks; Codex's Windows sandbox; and the findings, each with
its fix and a basis (verified, documented or not verified). Inside a WSL distribution: the WSL
side. On Linux and macOS: nothing to check. Exit 1 on an error or a warning. --live proves the
shell choice of each agent with its real binary through a fake API on 127.0.0.1 (no token is
spent), using the first of its kind on PATH or the path an option names. --markdown is the
short report to paste into any agent (at most 12 findings, or --max <n>); it always exits 0
agents [--agent ...]
every program of each agent, alone: which one runs first
explain [--list [<part>] | <finding-id>]
what a finding means, what the agent does in that case (each line marked verified,
documented or not verified), the fix, and the issue and forum threads
Options:
--json one JSON document on stdout (every command)
--project <dir> the folder the agents start in (default: the folder you are in); check
--agent <list> which agents: claude, codex, gemini, copilot, cursor, or all (the default); check and agents
--live run each agent's real binary against a fake API; check
-h, --help this text
-v, --version the version
Finding ids are <agent>.<name>, for example claude.git-bash-not-found or codex.sandbox-none.
Exit codes: 0 ok; 1 findings, or a failed proof; 2 a usage error or an unexpected error.
Environment: AGENT_WINDOWS_DOCTOR_MANAGED_DIR (the folder to read Claude Code's managed-settings.json from, instead of
C:\Program Files\ClaudeCode) and AGENT_WINDOWS_DOCTOR_USER_ENV_FILE (a JSON file that stands in for HKCU\Environment);
the tests use both. It reads files, the registry and the output of a few Windows programs, and writes nothing. It
starts an agent's binary only with --live, in throwaway folders, and sends nothing to anyone.
The examples below were run in a throwaway Windows user, shown as C:\Users\me: the real machine (Windows 11, no PowerShell 7, Git for Windows at E:\Das\Git, not on PATH), Claude Code 2.1.302 in .local\bin, Codex CLI, Gemini CLI and Copilot CLI installed by npm and reached from AppData\Roaming\npm (a junction to the install the tests use), the Cursor CLI launcher as an earlier install left it, and a project, C:\Users\me\shop, whose hooks were written for bash. HOME, USERPROFILE, LOCALAPPDATA, APPDATA and every agent's folder pointed into that user's folder, and PATH held the system folders, Node and the two folders of the agents.
All five agents in one run is a long report; this is one agent, whole.
$ agent-windows-doctor check --agent codex --project C:\Users\me\shop
agent-windows-doctor 1.4.1 what the coding agents get on this Windows machine
project C:\Users\me\shop
system Windows 11 Home Single Language (10.0.26300), x64
agents Codex CLI
The machine
Windows Windows 11 Home Single Language, build 26300.9711 (26H2), x64
Codex MXC sandbox can run here: build 26300 is newer than the documented minimum builds (26100.9278 or 26200.9278)
Windows PowerShell 5.1 C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe 5.1.26100.9711, FullLanguage, execution policy RemoteSigned (MachinePolicy Undefined, UserPolicy Undefined, Process Undefined, CurrentUser RemoteSigned, LocalMachine Undefined)
PowerShell 7 not found: no pwsh.exe on PATH, in %ProgramFiles%\PowerShell\7, in the Store alias folder or in .dotnet\tools
Git for Windows E:\Das\Git\bin\bash.exe; git is not on PATH
bash.exe on PATH 1 C:\Windows\System32\bash.exe the WSL launcher (starts the default distribution)
long paths Windows LongPathsEnabled on; git core.longpaths not read (git is not on PATH)
line endings git core.autocrlf not read (git is not on PATH)
console code page 437, system OEM 437, ANSI 1252
WSL * docker-desktop Running WSL 2 (made by another tool, not one to work in)
Node 24.19.0, npm 11.17.0; installed packages ask for: Codex CLI >=16 (met)
Every agent on this machine
Codex CLI
1 C:\Users\me\AppData\Roaming\npm\codex.cmd
0.163.0 (package.json of @openai/codex); npm (global); runs first from a terminal
leads to C:\Users\me\AppData\Roaming\npm\node_modules\@openai\codex\bin\codex.js -> C:\Users\me\AppData\Roaming\npm\node_modules\@openai\codex-win32-x64\vendor\x86_64-pc-windows-msvc\bin\codex.exe
2 C:\Program Files\WindowsApps\OpenAI.Codex_26.1007.9446.0_x64__2p2nqsd0c76g0\app\resources\codex.exe
26.1007.9446.0 (app package: the version of the app, not of the CLI inside it); bundled by the Codex desktop app (Microsoft Store package OpenAI.Codex); not on PATH
The shell each agent gives the model
Codex CLI (tool exec_command)
shell Windows PowerShell 5.1 (powershell.exe) at C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe, started with -Command [verified]
- no pwsh.exe on PATH and no PowerShell 7 in C:\Program Files, so the first powershell.exe on PATH, by its full path
- the order: pwsh.exe on PATH, C:\Program Files\PowerShell\7\pwsh.exe, powershell.exe on PATH, C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
- a pwsh.exe in the project folder, or in a ProgramFiles folder that an environment variable names, is not used
- there is no key to choose the shell: windows.shell_path is not a key of Codex CLI 0.163.0
Hooks on Windows
Codex CLI
files: user C:\Users\me\.codex\config.toml
PreToolUse [*] [ -f package.json ] && node hooks/check.mjs
in C:\Users\me\.codex\config.toml; run by Windows PowerShell 5.1 [verified]; looks like bash: and-or, test
Codex on Windows
CODEX_HOME C:\Users\me\.codex (CODEX_HOME)
files read user C:\Users\me\.codex\config.toml
project trust not trusted: the project's .codex folder is not read
sandbox none selected; sandbox_mode "workspace-write" becomes read-only
MXC available (build 26300 is newer than the documented minimum builds (26100.9278 or 26200.9278); no sandbox log yet)
shell key windows.shell_path in C:\Users\me\.codex\config.toml: Codex CLI has no such key
more codex doctor reports the install, the sign-in, the runtime, Microsoft Defender exclusions and Dev Drive
Findings
warning codex.sandbox-none [verified]
No Windows sandbox is selected for Codex CLI (windows.sandbox and features.prefer_mxc are not set in C:\Users\me\.codex\config.toml), so the sandbox mode "workspace-write" becomes read-only.
what Codex CLI does: The header of codex exec said "sandbox: read-only" and the sandbox log said "Windows sandbox selection: disabled" (put to Codex 0.163.0). The documentation says that with none selected Codex prefers elevated, whose setup needs an administrator.
fix: [features] prefer_mxc = true in C:\Users\me\.codex\config.toml (Windows 11 at or above 26100.9278 or 26200.9278), or [windows] sandbox = "unelevated". Run codex doctor for the rest of the install.
more: agent-windows-doctor explain codex.sandbox-none
warning codex.shell-path-ignored [verified]
windows.shell_path is set in C:\Users\me\.codex\config.toml, and Codex CLI does not have that key.
what Codex CLI does: codex exec --strict-config stops with: unknown configuration field `windows.shell_path`; without it the key is ignored and the model's commands ran in PowerShell all the same (put to Codex 0.163.0). The key is proposed in openai/codex#16579.
fix: Remove the key. Codex has no setting for its shell on Windows; to run a command in Git Bash, start Git Bash from PowerShell: & 'C:\Program Files\Git\bin\bash.exe' -lc '...'.
more: agent-windows-doctor explain codex.shell-path-ignored
warning codex.hook-bash-syntax [verified]
1 hook command looks like bash, and Codex CLI runs it through PowerShell (Codex CLI runs a hook command through the PowerShell it finds for its shell tool): PreToolUse in C:\Users\me\.codex\config.toml: [ -f package.json ] && node hooks/check.mjs (&& and || are not valid in Windows PowerShell 5.1 (they exist from PowerShell 7); [ ... ], [[ ... ]] and test are bash tests).
what Codex CLI does: Codex CLI ran its hook command through PowerShell (put to the binary), so bash syntax is a PowerShell error or an empty variable.
fix: Write the hook as one program and its arguments (node "hooks/check.mjs"), which both shells read the same way; use $env:NAME for a variable, and one command per line instead of && in Windows PowerShell 5.1.
more: agent-windows-doctor explain codex.hook-bash-syntax
note codex.hook-exit-2 [verified]
1 Codex hook can block (PreToolUse in C:\Users\me\.codex\config.toml), and on Windows a hook that exits with code 2 does not.
what Codex CLI does: The documentation says exit code 2 blocks a PreToolUse call. Codex CLI 0.163.0 on Windows ran the command all the same, for exit codes 2 and 1: PowerShell turns the code into 1. A hook that prints JSON with permissionDecision deny (or decision block) on stdout and exits 0 did block (put to the binary).
fix: Block with JSON: {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}} on stdout, and exit 0.
more: agent-windows-doctor explain codex.hook-exit-2
note codex.hook-trust [verified]
1 Codex hook (PreToolUse in C:\Users\me\.codex\config.toml) run only after you have reviewed and trusted it.
what Codex CLI does: A hook that had not been trusted did not run, and the command it guarded ran (put to Codex CLI 0.163.0); with --dangerously-bypass-hook-trust it ran. Codex records trust against each hook's hash, so a changed hook is skipped until it is reviewed again.
fix: Review them in the CLI with /hooks. Managed hooks (requirements.toml) are trusted by policy.
more: agent-windows-doctor explain codex.hook-trust
0 errors, 3 warnings, 2 notes
Without --agent, check reports the agents it finds on the machine; --agent claude,codex (or all) names them, found or not. --project is the folder the agents start in, for the project's settings and hook files; the default is the folder you are in. The exit code is 1 when there is an error or a warning, and 0 for notes alone.
The other four agents' sections look the same. In the same user, the findings for the other agents and the machine included these, each with its fix and a page for explain: copilot.hook-bash-only (an error: a bash field with no powershell field, and the first bash.exe on PATH is the WSL launcher, so the hook fails and Copilot CLI denies the tool call), cursor.launcher-no-version (the launcher's version pattern does not match the folder the installer made), claude.git-bash-not-found (Git for Windows is installed, and git.exe is not on PATH), copilot.claude-hooks (Copilot CLI also runs the hooks of .claude/settings.json, through PowerShell, where $CLAUDE_PROJECT_DIR is empty) and claude.hook-bash-syntax.
--live starts each agent's real binary (the first of its kind on PATH, or the path that --claude, --codex, --gemini or --copilot names) in a throwaway home, against a fake API on 127.0.0.1, and compares what happened with the prediction: for Claude Code the shell tools its first request lists and the bash.exe it says it took, for Codex CLI, Gemini CLI and Copilot CLI the PowerShell that ran the one shell command the fake model asks for, which prints its own command line. This is the part of the report that --live adds (the other sections are as above):
$ agent-windows-doctor check --live --agent claude,codex,gemini,copilot --project C:\Users\me\shop
Live: the real binaries, through a fake API on 127.0.0.1 (no token spent)
Claude Code proved with the real claude 2.1.302 (C:\Users\me\.local\bin\claude.exe; the first on PATH):
the first request listed PowerShell; PowerShell edition: Windows PowerShell 5.1 (powershell.exe)
the fake API has no account, so the account's default for the PowerShell tool is off there; predicted: PowerShell
Codex CLI proved with the real codex 0.163.0 (C:\Users\me\AppData\Roaming\npm\codex.cmd; the first on PATH):
the shell command ran in C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe; predicted: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
command line: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -Command '[Environment]::CommandLine'
Gemini CLI proved with the real gemini 0.64.0 (C:\Users\me\AppData\Roaming\npm\gemini.cmd; the first on PATH):
the shell command ran in C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe; predicted: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
command line: C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -NonInteractive -Command [Environment]::CommandLine
GitHub Copilot CLI proved with the real copilot 1.0.98 (C:\Users\me\AppData\Roaming\npm\copilot.cmd; the first on PATH):
the shell command ran in powershell.exe; predicted: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
command line: "powershell.exe" -NoProfile -NoLogo -NonInteractive -Command "try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}; t…
Codex prints each backslash of the path in its command_execution item twice, as the line shows; the tool compares the path without the doubling. The Cursor CLI is never run (it needs a login), and an agent that is not on the machine is skipped, not failed. On Linux and macOS check --live gives the answer of plain check, nothing to check and exit code 0: there is nothing to prove there. Inside WSL it is refused with exit code 1, because the proof is of the Windows side. A named path that is not a file, a binary that does not run, or a run that differs from the prediction makes the exit code 1, with the difference in words. Before any binary starts, a guard checks that HOME, USERPROFILE, LOCALAPPDATA, APPDATA, CODEX_HOME, COPILOT_HOME, CLAUDE_CONFIG_DIR and GEMINI_CLI_HOME are inside the throwaway folder and that HTTP_PROXY, HTTPS_PROXY and ALL_PROXY are a closed port on 127.0.0.1 (see Privacy).
The short report to paste into any agent: at most 12 findings (errors and warnings; the notes stay in check), 500 characters a line, and exit code 0 always. --max <n> sets how many errors and warnings the report holds (default 12, minimum 1, whole numbers only); --max without --markdown is a usage error.
$ agent-windows-doctor check --markdown --project C:\Users\me\shop
## agent-windows-doctor check: C:\Users\me\shop
Windows 11 Home Single Language. Shell the model gets: Claude Code no Git Bash, PowerShell tool; Codex CLI PowerShell 5.1; Gemini CLI PowerShell 5.1; GitHub Copilot CLI PowerShell 5.1; Cursor PowerShell 5.1.
Claude Code 2.1.302 runs first; Codex CLI 0.163.0 runs first; Gemini CLI 0.64.0 runs first; GitHub Copilot CLI 1.0.98 runs first; Cursor 2026.06.16-20-30-07-a07d3ac is not on PATH. 2 errors, 8 warnings, 6 notes.
- ERROR `copilot.hook-bash-only`: 1 hook has a bash field and no powershell field (preToolUse in C:\Users\me\shop\.github\hooks\guard.json); on Windows Copilot CLI starts the first bash on PATH, which is the WSL launcher. Fix: Add a powershell field (or exec and args, which start the program with no shell) beside the bash field.
- ERROR `cursor.launcher-no-version`: The Cursor CLI launcher (C:\Users\me\AppData\Local\cursor-agent\cursor-agent.ps1) picks a version folder whose name matches ^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$, and none of the 1 folder in C:\Users\me\AppData\Local\cursor-agent\versions… Fix: Run the node.exe in the newest folder of versions directly: <folder>\node.exe <folder>\index.js, or reinstall the Cursor CLI.
- WARNING `claude.git-bash-not-found`: Git for Windows is installed (E:\Das\Git\bin\bash.exe), but Claude Code does not find it: git.exe is not on PATH. Fix: Set CLAUDE_CODE_GIT_BASH_PATH for every program: setx CLAUDE_CODE_GIT_BASH_PATH "E:\Das\Git\bin\bash.exe". Then open a new terminal.
- WARNING `cursor.not-on-path`: Cursor is installed (C:\Users\me\AppData\Local\cursor-agent\agent.cmd, the Cursor CLI installer, 2026.06.16-20-30-07-a07d3ac), but its folder is not on PATH. Effect: A terminal says "agent is not recognized". Fix: Add C:\Users\me\AppData\Local\cursor-agent to the user PATH (System Properties, Environment Variables), then open a new terminal.
- WARNING `codex.sandbox-none`: No Windows sandbox is selected for Codex CLI (windows.sandbox and features.prefer_mxc are not set in C:\Users\me\.codex\config.toml), so the sandbox mode "workspace-write"… Fix: [features] prefer_mxc = true in C:\Users\me\.codex\config.toml (Windows 11 at or above 26100.9278 or 26200.9278), or [windows] sandbox = "unelevated". Run codex doctor for the rest of the install.
- WARNING `codex.shell-path-ignored`: windows.shell_path is set in C:\Users\me\.codex\config.toml, and Codex CLI does not have that key. Fix: Remove the key. Codex has no setting for its shell on Windows; to run a command in Git Bash, start Git Bash from PowerShell: & 'C:\Program Files\Git\bin\bash.exe' -lc '...'.
- WARNING `copilot.claude-hooks`: Copilot CLI also runs the 1 hook of the project's .claude/settings.json (PreToolUse: $CLAUDE_PROJECT_DIR/hooks/guard.sh), through PowerShell, where $CLAUDE_PROJECT_DIR is empty. Fix: Write the command so that PowerShell and bash both read it (node "hooks/check.mjs", with no shell variable), or use $env:CLAUDE_PROJECT_DIR for PowerShell.
- WARNING `cursor.launcher-quotes`: The Cursor CLI launcher (C:\Users\me\AppData\Local\cursor-agent\agent.cmd) starts Windows PowerShell 5.1 with delayed expansion on and %* for the arguments, which drops a double quote and eats an exclamation mark. Fix: Avoid " and ! in what you pass to agent, or start it the way a staff member suggested: run the node.exe of the newest folder in the versions folder directly with its index.js.
- WARNING `claude.hook-bash-syntax`: 1 hook command looks like bash, and Claude Code runs it through PowerShell (Git Bash was not found, so Claude Code runs a hook command through PowerShell, with -ExecutionPolicy Bypass): PreToolUse in .claude\settings.json:… Fix: Write the hook as one program and its arguments (node "hooks/check.mjs"), which both shells read the same way; use $env:NAME for a variable, and one command per line instead of && in Windows PowerShell 5.1.
- WARNING `codex.hook-bash-syntax`: 1 hook command looks like bash, and Codex CLI runs it through PowerShell (Codex CLI runs a hook command through the PowerShell it finds for its shell tool): PreToolUse in C:\Users\me\.codex\config.toml: [… Fix: Write the hook as one program and its arguments (node "hooks/check.mjs"), which both shells read the same way; use $env:NAME for a variable, and one command per line instead of && in Windows PowerShell 5.1.
One page per finding id: what it means, what the agent does (each line marked verified, documented or not verified), the fix, and the issue or forum threads. explain --list prints the 77 ids with their titles; explain --list <part> filters that list to every id containing the part, without regard to case and with surrounding whitespace ignored. An id is matched without regard to case, a unique part of an id matches the page, an ambiguous part lists every matching id in page order, and an unknown id suggests the nearest ids.
$ agent-windows-doctor explain codex.shell-path-ignored
codex.shell-path-ignored windows.shell_path is set, and Codex does not have that key
Codex runs the model's commands in PowerShell on Windows. A windows.shell_path key was proposed to point it at Git Bash or another shell (issue 16579); Codex CLI 0.163.0 does not have it.
What Codex CLI does
- (verified) codex exec --strict-config stopped with `unknown configuration field `windows.shell_path`` for the key in config.toml and for -c windows.shell_path=...; without --strict-config the key was ignored and the command ran in PowerShell: the command_execution item of codex exec --json named "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -Command '...' (it prints each backslash of the path twice).
- (verified) `shell`, `windows.shell` and `windows.default_shell` are unknown configuration fields as well; `features.powershell_shell_version` is a feature flag (under development).
- (documented) the configuration reference lists windows.sandbox and, in requirements.toml, windows.allowed_sandbox_implementations and windows.allow_mxc under windows; no key names a shell.
The fix
- Remove the key. Codex has no setting for its shell on Windows.
- To run something in Git Bash, start Git Bash from PowerShell: & 'C:\Program Files\Git\bin\bash.exe' -lc '...'.
The issue and forum threads
#16579 Windows: allow configuring the default session shell via config (open, 54 reactions)
https://github.com/openai/codex/issues/16579
The inventory alone, for "which claude runs first": the second section of check, with --agent to name the agents.
$ agent-windows-doctor agents
agent-windows-doctor 1.4.1 every agent on this machine
Claude Code
1 C:\Users\me\.local\bin\claude.exe
2.1.302 (file version); the native installer; runs first from a terminal
Codex CLI
1 C:\Users\me\AppData\Roaming\npm\codex.cmd
0.163.0 (package.json of @openai/codex); npm (global); runs first from a terminal
leads to C:\Users\me\AppData\Roaming\npm\node_modules\@openai\codex\bin\codex.js -> C:\Users\me\AppData\Roaming\npm\node_modules\@openai\codex-win32-x64\vendor\x86_64-pc-windows-msvc\bin\codex.exe
2 C:\Program Files\WindowsApps\OpenAI.Codex_26.1007.9446.0_x64__2p2nqsd0c76g0\app\resources\codex.exe
26.1007.9446.0 (app package: the version of the app, not of the CLI inside it); bundled by the Codex desktop app (Microsoft Store package OpenAI.Codex); not on PATH
Gemini CLI
1 C:\Users\me\AppData\Roaming\npm\gemini.cmd
0.64.0 (package.json of @google/gemini-cli); npm (global); runs first from a terminal
leads to C:\Users\me\AppData\Roaming\npm\node_modules\@google\gemini-cli\bundle\gemini.js
GitHub Copilot CLI
1 C:\Users\me\AppData\Roaming\npm\copilot.cmd
1.0.98 (package.json of @github/copilot); npm (global); runs first from a terminal
leads to C:\Users\me\AppData\Roaming\npm\node_modules\@github\copilot\npm-loader.js -> C:\Users\me\AppData\Roaming\npm\node_modules\@github\copilot-win32-x64\copilot.exe
Cursor
1 C:\Users\me\AppData\Local\cursor-agent\agent.cmd
2026.06.16-20-30-07-a07d3ac (versions folder); the Cursor CLI installer; not on PATH
also answers to cursor-agent.cmd
2 C:\Program Files\cursor\Cursor.exe
2.3.34 (file version); the Cursor editor; not on PATH
none of them is on PATH, so a terminal does not know the command agent
Every command takes --json and prints one document. A usage error is { ok, error }. ok is false when there are findings of severity error or warning, or a proof failed. The shapes (a ? marks a field that some documents lack; what follows // is a comment):
check { ok, applicable, platform, kind?, project?, // kind: "windows", "wsl"; on Linux and macOS applicable is false and message says there is nothing to check
agents?: [ "claude" | "codex" | "gemini" | "copilot" | "cursor" ], asked?: [], notFound?: [],
system?: { os, release, arch, wsl? },
machine?: { windows: { ok, caption, release, build, ubr, display, arch, server }, mxc: { supported, why },
powershell: [ { id, label, path, how, onPath, pathIndex?, probe: { ok, exe, edition, version, languageMode, effective, list: [ { scope, policy } ], error } } ],
git: { ok, version, autocrlf?: { origin, value }, longpaths?: { origin, value }, error }, gitInstalls: [ { path, via } ], bash: [ { path, kind } ],
longPaths: { ok, enabled, set }, codePage: { ok, active, oem, ansi, error }, wsl: { available, distros: [ { name, isDefault, state, version, internal } ], defaultName, workable: [], error },
node: { node, nodePath, npm, npmPrefix, npmFound }, nodeWants: [ { agent, package, version, path, range, ok } ] },
inventory?: { claude?, codex?, gemini?, copilot?, cursor?: { label, first, entries: [ { path, kind, origin, onPath, runsFirst, leadsTo: [], package: { name, version, engines: { node } }, version, versionFrom, asked, note, skipped } ] } },
shells?: { claude?: { tools: { Bash, PowerShell }, fatal, gitBash: { found, path, how, variable: { value, source, ignored: [] }, variableState, trail: [ { step, state?, value?, source?, hint?, path?, exists?, lines?: [], skipped?: [], chosen? } ], starts: { checked, ok, error }, onMachine: [ { path, via } ] },
powershell: { setting, state, why, unread, variable: { value, source, ignored: [] }, edition, path, how }, deny: { Bash: [], PowerShell: [] }, defaultShell: [ { scope, display, value, valid } ], why: [], basis },
codex?, gemini?, copilot?, cursor?: { tool, state, shell: { label, path, core }, args, how: [], basis } },
hooks?: { claude?, codex?, gemini?, copilot?, cursor?: { files: [ { scope, path, display, usable, error, applied } ],
entries: [ { event, matcher, scope, source, field, command, runner: { via, exe, edition, args, basis, why }, bashReasons: [], policy, ps1, extensionless } ] } },
userEnv?: { ok, layer, error, entries: [], own },
findings?: [ { id, severity, agent, basis, message, effect, fix } ], counts?: { error, warning, note },
codex?: { home: { dir, source, given }, trusted, sandbox: { configured, preferMxc, selection, selectionHow, preferUnavailable, mode: { asked, wanted, effective, downgraded }, mxcAvailable, mxc: { supported, why }, log },
shellKeys: [ { key, value, layer, file } ], files: [ { kind, path, ok, error } ] },
gemini?: { pty: [ { pkgDir, found } ] },
claude?: { settings: { configDir, configSource, files: [ { scope, label, display, path, exists, usable, error, applied? } ], managedApplied }, executionPolicy: { ok, list: [ { scope, policy } ], respect, effective: { scope, policy, bypassed, underneath: { scope, policy } }, machine: { scope, policy, bypassed } },
indexLock: { ok, gitDir, lock }, bashStarts: { checked, ok, error } },
cursor?: { launcher: { dir, cmdFile, ps1File, powershell, args, delayedExpansion, forwardsArgs, versionPattern, versions: [], matching: [], noVersionFolder }, msystem },
live?: { ok, agents: { claude?, codex?, gemini?, copilot?: { ok, error?, binary: { path, version, asked, note, source }, proved, predicted: { tools?: [], exit?, bash?, how?, shell?, state? }, actual: { tools?: [], exit?, bash?, shell?, error? }, differences: [], exitCode, requests, line, edition, warning },
cursor?: { ok?, skipped } } },
locations?: { configDir, configSource, configOnWindows, project, projectOnWindows }, wslInherits?: [], codexHome?: { dir, source }, message?, refused? }
agents { ok, applicable, platform, agents: [], message?, // the same inventory as in check
inventory?: { claude?, codex?, gemini?, copilot?, cursor?: { label, first, entries: [ { path, kind, origin, onPath, runsFirst, leadsTo: [], package?: { name, version, engines: { node } }, version, versionFrom, asked, note, skipped } ] } },
firsts?: { claude?, codex?, gemini?, copilot?, cursor? } }
explain { ok, id?, agent?, basis?, title?, means?, does?: [ { source, text } ], fix?: [], issues?: [ { number, title, url } ], // one page
findings?: [ { id, title, basis } ], // --list
refused?, ids?: [], near?: [] } // an id that is not one
error { ok, error }
77 findings, by agent. The severity is the usual one (a few depend on the case: a bash field in a Copilot hook is an error where the tool call is denied, a warning where it is not, and a note where a Git Bash runs it). The basis is verified when the binary showed it in the tests, documented when the vendor's documentation or a maintainer's words say it, and not verified when neither; explain <id> marks each line of a page the same way. The five machine-wide checks that claude-windows-doctor made (long paths, core.autocrlf, the execution policy, the code page, a stale .git\index.lock) and its user-environment and WSL checks are under claude., and are reported when Claude Code is among the agents checked.
| Id | Severity | Basis | What it says |
|---|---|---|---|
claude.no-shell-tool |
error | verified | Claude Code will not start: no Git Bash and the PowerShell tool is off |
claude.git-bash-not-found |
warning | verified | Git for Windows is installed, but Claude Code does not find its bash.exe |
claude.git-bash-path-ignored |
warning | verified | CLAUDE_CODE_GIT_BASH_PATH is set, and Claude Code ignores it |
claude.git-bash-path-project-settings |
warning | verified | CLAUDE_CODE_GIT_BASH_PATH is in a project or local settings file, where it is not used |
claude.git-bash-broken |
warning | verified | Git Bash is found, and does not start |
claude.powershell-variable-unread |
warning | verified | CLAUDE_CODE_USE_POWERSHELL_TOOL is set to a value Claude Code does not read |
claude.default-shell-scope |
note | verified | defaultShell is not the shell the model gets |
claude.powershell-5 |
note | verified | The PowerShell tool runs Windows PowerShell 5.1 |
claude.wsl-bash-on-path |
note | verified | bash.exe on PATH is the WSL launcher |
claude.launcher-first |
warning | not verified | The first claude on PATH is not the command line |
claude.not-on-path |
warning | verified | claude is installed and its folder is not on PATH |
claude.multiple |
warning | documented | More than one claude command line is installed |
claude.older-first |
warning | verified | The claude that runs first is older than another one |
claude.no-version |
warning | verified | A claude on PATH does not print a version |
claude.node-for-npm |
note | verified | Node is older than the npm package of Claude Code wants |
claude.long-paths |
note | verified | Long paths are not switched on |
claude.autocrlf |
note | verified | git converts line endings (core.autocrlf is true) |
claude.execution-policy |
warning | verified | The PowerShell execution policy blocks scripts |
claude.code-page |
note | verified | The console code page is not UTF-8 |
claude.index-lock |
warning | not verified | A stale .git\index.lock |
claude.user-env-not-in-process |
warning | documented | A variable of the Windows user environment is not in this process |
claude.user-env-differs |
warning | documented | A variable differs between the user environment and this process |
claude.process-env-not-in-user |
note | documented | A setting exists in this process only |
claude.wsl-no-distro |
note | documented | WSL has only the distributions of other tools |
claude.wsl-windows-claude |
warning | not verified | In WSL, claude is the Windows one |
claude.wsl-config-on-mnt |
warning | documented | In WSL, the config folder or the project is on a Windows drive |
claude.wsl-inherits-windows |
note | documented | WSL reads the Windows managed settings |
claude.hook-bash-syntax |
warning | verified | A Claude Code hook command is a bash line, and Claude Code runs it through PowerShell |
claude.hook-extensionless |
warning | documented | A Claude Code hook starts a file that has no extension |
codex.not-on-path |
warning | verified | Codex CLI is installed and its folder is not on PATH |
codex.multiple |
warning | documented | More than one Codex CLI command line is installed |
codex.older-first |
warning | verified | The codex that runs first is older than another one |
codex.no-version |
warning | verified | The Codex CLI command on PATH does not print a version |
codex.node-for-npm |
note | verified | Node is older than the npm package of Codex CLI wants |
codex.sandbox-none |
warning | verified | Codex has no Windows sandbox selected, and workspace-write becomes read-only |
codex.mxc-unavailable |
warning | documented | MXC is selected, and this Windows is too old for it |
codex.shell-path-ignored |
warning | verified | windows.shell_path is set, and Codex does not have that key |
codex.home-wsl |
note | documented | CODEX_HOME is set on Windows, and WSL can read it |
codex.hook-exit-2 |
note | verified | A Codex hook that exits with code 2 does not block on Windows |
codex.hook-trust |
note | verified | A Codex hook runs only after you have trusted it |
codex.hook-bash-syntax |
warning | verified | A Codex CLI hook command is a bash line, and Codex CLI runs it through PowerShell |
codex.hook-ps1-policy |
warning | documented | A Codex CLI hook runs a .ps1 script, and the execution policy blocks it |
codex.hook-extensionless |
warning | documented | A Codex CLI hook starts a file that has no extension |
gemini.not-on-path |
warning | verified | Gemini CLI is installed and its folder is not on PATH |
gemini.multiple |
warning | documented | More than one Gemini CLI command line is installed |
gemini.older-first |
warning | verified | The gemini that runs first is older than another one |
gemini.no-version |
warning | verified | The Gemini CLI command on PATH does not print a version |
gemini.node-for-npm |
note | verified | Node is older than the npm package of Gemini CLI wants |
gemini.powershell-not-on-path |
error | verified | Gemini CLI finds no PowerShell on PATH |
gemini.constrained-language-mode |
error | documented | PowerShell is in Constrained Language Mode, which Gemini CLI cannot work in |
gemini.node-pty-missing |
warning | documented | The Gemini CLI install has no node-pty binary |
gemini.hook-bash-syntax |
warning | verified | A Gemini CLI hook command is a bash line, and Gemini CLI runs it through PowerShell |
gemini.hook-ps1-policy |
warning | documented | A Gemini CLI hook runs a .ps1 script, and the execution policy blocks it |
gemini.hook-extensionless |
warning | documented | A Gemini CLI hook starts a file that has no extension |
copilot.not-on-path |
warning | verified | GitHub Copilot CLI is installed and its folder is not on PATH |
copilot.multiple |
warning | documented | More than one GitHub Copilot CLI command line is installed |
copilot.older-first |
warning | verified | The copilot that runs first is older than another one |
copilot.no-version |
warning | verified | The GitHub Copilot CLI command on PATH does not print a version |
copilot.node-for-npm |
note | verified | Node is older than the npm package of GitHub Copilot CLI wants |
copilot.powershell-not-on-path |
error | verified | Copilot CLI finds neither pwsh.exe nor powershell.exe |
copilot.pwsh-missing |
note | verified | Copilot CLI asks for PowerShell 6 or newer, and this machine has only Windows PowerShell 5.1 |
copilot.hook-bash-only |
error, warning, note | verified | A Copilot hook has a bash field and no powershell field |
copilot.claude-hooks |
warning, note | verified | Copilot CLI also runs the hooks of .claude/settings.json |
copilot.hook-bash-syntax |
warning | verified | A GitHub Copilot CLI hook command is a bash line, and GitHub Copilot CLI runs it through PowerShell |
copilot.hook-ps1-policy |
warning | verified | A GitHub Copilot CLI hook runs a .ps1 script, and the execution policy blocks it |
copilot.hook-extensionless |
warning | documented | A GitHub Copilot CLI hook starts a file that has no extension |
cursor.not-on-path |
warning | verified | Cursor is installed and its folder is not on PATH |
cursor.multiple |
warning | documented | More than one Cursor command line is installed |
cursor.older-first |
warning | verified | The agent that runs first is older than another one |
cursor.no-version |
warning | verified | The Cursor command on PATH does not print a version |
cursor.launcher-quotes |
warning | documented | The Cursor CLI launcher drops double quotes and eats exclamation marks |
cursor.launcher-no-version |
error | verified | The Cursor CLI launcher cannot pick a version folder |
cursor.msystem |
warning | documented | MSYSTEM is set, and Cursor runs its hooks with bash |
cursor.hook-stdin-bom |
note | documented | A Cursor hook's stdin starts with a byte order mark on Windows |
cursor.hook-bash-syntax |
warning | documented | A Cursor hook command is a bash line, and Cursor runs it through PowerShell |
cursor.hook-ps1-policy |
warning | documented | A Cursor hook runs a .ps1 script, and the execution policy blocks it |
cursor.hook-extensionless |
warning | documented | A Cursor hook starts a file that has no extension |
check collects the facts first, from the machine, and judges them after; the same facts feed the text, the markdown and the JSON.
- The machine. The release and build of Windows from the operating system, and the update revision (
UBR) andDisplayVersionfrom the registry; the PowerShell editions: Windows PowerShell 5.1 at%SystemRoot%\System32\WindowsPowerShell\v1.0and everypwsh.exethatwhere.exelists or an installer's folder holds, each asked once for its edition, language mode andGet-ExecutionPolicy -List; Git for Windows from the registry,where.exe gitand the usual folders; everybash.exeon PATH, told apart (Git Bash, or the WSL launcher inSystem32);LongPathsEnabled;git configforcore.autocrlfandcore.longpaths;chcp;wsl.exe -l -v; Node and npm. - Every agent. The folders of PATH in order, trying each name in the order of
PATHEXTascmd.exedoes (test/windows.test.mjsstarts one command from several folders incmd.exeand compares, and compares the folders and files withwhere.exe), then the places the installers use: npm's global folder, WinGet, the native installers, the desktop apps' bundles under%APPDATA%\Claude\claude-codeand the Microsoft Store package of Codex, the cache thatcopilot.exeunpacks under%LOCALAPPDATA%\copilot\pkg, the Cursor CLI folder%LOCALAPPDATA%\cursor-agent. An npm.cmdshim is followed to the JavaScript it starts and the program next to it. A version is read from thepackage.jsonof the package, the version resource of an.exe, or the name of a version folder, and the report says which; onlycheck --liveasks the program (--version). - The shell. For Claude Code, the search it makes for Git Bash, in its order, and the rules for its PowerShell tool (the 129 cases of
test/shell-table.mjs, put to the binary in claude-windows-doctor and held to the prediction here). For Codex CLI, Gemini CLI and Copilot CLI, how each finds a PowerShell, from the runs described below. For Cursor, the launcher read from disk. - Hooks. The files each agent reads: Claude Code's settings (user, project, local, managed); Codex's
config.toml(user, system underProgramData, and a trusted project's) andhooks.json, withcommand_windows; Gemini CLI's settings (system, user, project); Copilot CLI'sCOPILOT_HOME\hooks,.github\hooks, the policy folder and the project's.claude\settings.json; Cursor'shooks.jsonfiles. Each command is judged: what starts it, whether it looks like bash, whether it starts a.ps1under a blocking execution policy, whether it starts a file with no extension. - Codex on Windows.
CODEX_HOME, the layers ofconfig.tomlmerged as Codex merges them, the keyswindows.sandboxandfeatures.prefer_mxc, the sandbox log underCODEX_HOME\.sandbox, and the build of Windows against the MXC minimum. - Findings. One function per agent turns facts into findings with an id, a severity, a message, what the agent does, the fix and a basis. The basis comes from the
explainpage of the id, so the report and the page cannot disagree. --live. The guard, a fake API per agent on 127.0.0.1, the real binary, the shell command that prints its own command line, the comparison with the prediction.
On Linux and macOS, check says there is nothing to check and exits 0. Inside a WSL distribution it looks at the WSL side of Claude Code (a Windows claude reached through /mnt, a config folder or a project on a Windows drive, the Windows managed settings) and at CODEX_HOME when it is on a Windows drive. The Windows logic is written against a sys object (platform, environment, files, programs), so the tests put a pretend Windows machine in its place and run it on every system; AGENT_WINDOWS_DOCTOR_MANAGED_DIR and AGENT_WINDOWS_DOCTOR_USER_ENV_FILE let a test (or you) point two reads somewhere else.
It sends nothing anywhere, has no telemetry and no account, and has no network code except the local connection of check --live (a server on 127.0.0.1 for the fake API, in src/live.mjs, the only file that opens a connection).
- It reads the settings and hook files named above (only the keys that matter), the registry (the values of
HKCU\Environment,LongPathsEnabled, the code pages, the build of Windows, the managed settings of Claude Code, where Git for Windows is installed), the folders of PATH and of the installers, thepackage.jsonof npm packages, the version resource of programs, Codex's sandbox log, and the output ofwhere.exe,reg.exe query,git,powershell.exe(and the firstpwsh.exe),cmd.exe /c chcp,wsl.exe -l -v,npm, andGet-AppxPackagefor the Codex desktop app. No file is shown whole, and an environment value is printed only when its name says it is a path or a switch: a token inANTHROPIC_API_KEYshows as "set". - It writes nothing of yours.
src/write.mjsis the only code that writes, and it makes the throwaway folders ofcheck --live, under the system temp folder, and removes them afterwards. A test fails if a file other thansrc/write.mjscan write, a file other thansrc/sys.mjsandsrc/live.mjscan start a program, or a file other thansrc/live.mjscan open a connection. - It starts an agent only with
--live. The binary gets a dummy key, a throwaway home and project folder, and none of your agent variables; your hooks do not run. The guard refuses to start anything unless every home folder is inside the throwaway folder and every proxy is a closed port on 127.0.0.1 (withNO_PROXY=127.0.0.1,localhost): a probe on 17 October 2026 wrote into a real~/.codexbecauseHOMEwas set andCODEX_HOMEwas not, and the guard exists so that it cannot happen again. The Cursor CLI is never started. See SECURITY.md.
Run on 18 October 2026 on Windows 11 Home Single Language 10.0.26300.9711 (26H2), x64, with Node 24.19.0 and npm 11.17.0, Windows PowerShell 5.1.26100.9711 (this machine has no PowerShell 7) and Git for Windows 2.55.0 at E:\Das\Git (not at C:\Program Files\Git). Every real binary was started with spawn, with its standard input ignored, against a fake API on 127.0.0.1, in a throwaway HOME, USERPROFILE, LOCALAPPDATA, APPDATA and agent folder, behind the guard (test/sandbox.mjs), with every proxy a closed port: no model was reached and no token was spent. A "stand-in for PowerShell 7" below is a copy of powershell.exe named pwsh.exe in a folder of its own: those runs show which file an agent starts, not how it behaves in PowerShell 7.
A finding or a line of an explain page is marked verified only when a test put it to the binary: a test below, or, for the rows of Claude Code's table that none of them restates, the tests of claude-windows-doctor (the same table, put to Claude Code 2.1.295 and 2.1.299). What each binary was asked, and what it answered:
2.1.302 is the npm package @anthropic-ai/claude-code, whose bin is a native claude.exe; 2.1.299 is the bundle of the Claude desktop app (%APPDATA%\Claude\claude-code\2.1.299\f0584be0fbaf\claude.exe). test/e2e-claude.test.mjs ran with each, with the same result: every test passed.
- The shell tools of the first request. 37 rows of the table of 129 cases (
test/shell-table.mjs), one for each claim an explain page restates (test/claude-claims.mjsnames them): no Git Bash, found throughwhere git(cmd\git.exeyes,mingw64\bin\git.exeno, the order of two Gits, agit.exein the project folder skipped), the variable (forward and back slashes, a space in the path,git-bash.exe, quotes,%USERPROFILE%, a relative path, a missing file, an empty value ignored; a valid value winning over PATH), the variable in user, project and local settings (only the user's is used),CLAUDE_CODE_USE_POWERSHELL_TOOLwith the values it reads and does not read, and the whole-tool deny rules. For each, the binary listed the tools the table records, exited as it records, named thebash.exethe prediction names in its debug log, andpredictShellagreed. Two more rows apply only to a machine with Git atC:\Program Files\Git, which this one does not have; the CI runner has it, and 18 of the 39 rows apply there (the rows that are marked for a machine without Git in its usual folder do not apply there). The full table was put to Claude Code 2.1.295 and 2.1.299 in claude-windows-doctor, andtest/claude-shell.test.mjsholds all 129 rows to the prediction on a pretend machine. - The PowerShell tool's edition. Six PATH layouts: only
System32, apwsh.exeon PATH (a stand-in),PowerShell\7under the folderProgramW6432names,.dotnet\toolsin the home folder, apowershell.exefirst on PATH, andpwshafterpowershell: the second line of the tool's description named the edition the prediction named. With Windows PowerShell 5.1 the description says&&and||are not available. defaultShellwithbash,powershellandcmd(6 runs, two states): the same tools and a request of the same size as without it.- The Git of this machine (
E:\Das\Git), withcmdfirst on PATH,mingw64\binfirst andmingw64\binalone: found, not found, not found, as predicted. - The words of the debug log for a variable it ignores (
is not a bash/sh binaryforgit-bash.exe,not foundfor a missing file, each followed byfalling back to auto-detection) and for no Git Bash (Git Bash not found; BashTool will be unavailable). - The execution policy. A PowerShell hook and the PowerShell tool both saw
Process BypassinGet-ExecutionPolicy -List; withCLAUDE_CODE_POWERSHELL_RESPECT_EXECUTION_POLICYin the environment or in the user settings,Process Undefined. - Line endings, the code page, long paths. The Write tool wrote a new file with LF only and the Edit tool kept the CRLF of the file it changed;
cmd.exe /c dirthrough the Bash tool printed replacement characters forcafé-über.txtunder code page 437 while the PowerShell tool set 65001 and printed it right; the Write tool wrote a file at a path of 392 characters (on this machineLongPathsEnabledis on). - The words it stops with, with no Git Bash and
CLAUDE_CODE_USE_POWERSHELL_TOOL=0: exit 1, no request, "Claude Code on Windows requires a shell tool. Git Bash was not found and the PowerShell tool is disabled". claude doctornames no shell, PowerShell or bash in its 28 lines (36 for 2.1.299); for the desktop bundle it says "Native installation exists but ... is not in your PATH", for the npm package it does not.- The npm package. The inventory read version 2.1.302,
engines.node>=22.0.0and the program of its bin frompackage.json, as the binary prints it.
The npm package @openai/codex: codex.cmd starts bin\codex.js, which starts codex-win32-x64\vendor\x86_64-pc-windows-msvc\bin\codex.exe; codex --version prints codex-cli 0.163.0, and the inventory read the same number from package.json. test/e2e-codex.test.mjs (9 tests) ran codex exec --json against a fake Responses API:
- The shell. The
command_executionitem names the PowerShell that ran the command, by its full path (Codex prints each backslash of it twice). On this machine, which has no PowerShell 7 inC:\Program Files, seven rows: with nopwsh.exeon PATH, thepowershell.exeofSystem32; a stand-inpwsh.exefirst on PATH, that file; apwsh.exeand apowershell.exein one folder first on PATH, thepwsh.exe; a copy ofpowershell.exefirst on PATH and nopwsh.exe, that copy; and thepowershell.exeofSystem32again when apwsh.exewas only in aProgramFilesfolder thatProgramFilesandProgramW6432name, when one was in the project folder, and when the PowerShell folder was off PATH. The same seven rows ran in run 39105519000 of thee2eworkflow, onwindows-latest(Windows Server 2025, 10.0.26100, Node 22.23.3), which has PowerShell 7 inC:\Program Files\PowerShell\7and starts its own steps with it; the tests expect that file wherever PATH did not list apwsh.exe(run 39103453346 was the first to show it), and they passed. Codex startedC:\Program Files\PowerShell\7\pwsh.exein those five rows, among them the one with a copy ofpowershell.exefirst on PATH, so the path written into Codex comes before apowershell.exeon PATH. That path andC:\Windows\System32\WindowsPowerShell\v1.0\powershell.exeare text incodex.exe, once each (the first test of the file searches the program for them). The prediction named the same file in each row on both machines. - No key chooses it.
windows.shell_path(inconfig.tomland as-c windows.shell_path=...),windows.shell,windows.default_shellandshelleach stoppedcodex exec --strict-configwithunknown configuration fieldand no command ran; without--strict-configthe key was ignored and the command ran in PowerShell.features.powershell_shell_versionis accepted (a feature flag). - Hooks. A PreToolUse hook that exited with 2, and one that exited with 1, did not stop the command; a hook that printed
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny",...}}, and one that printed{"decision":"block",...}, did, and the model got "Command blocked by PreToolUse hook: ".command_windowsandcommandWindowsreplacedcommand; ahooks.jsoninCODEX_HOMEwas read. The parent of the hook process was the file that ran the shell tool,powershell.exe -NoProfile -Command ...(the stand-inpwsh.exewhen PATH had one), and, with Windows PowerShell 5.1, a hook command with&&did not run while the command it guarded did (on thewindows-latestrunner, where Codex's shell is PowerShell 7, the same hook ran: PowerShell 7 reads&&, andtest/windows.test.mjsputnode -e "0" && echo doneto itspwsh.exethere). Without--dangerously-bypass-hook-trustthe hook did not run. - The Windows sandbox. With
sandbox_mode = "workspace-write"and nothing selected, the header ofcodex execsaidsandbox: read-onlyandCODEX_HOME\.sandbox\sandbox.<date>.logsaidWindows sandbox selection: disabled;windows.sandbox = "unelevated"gaveworkspace-writeandunelevated;features.prefer_mxc = trueandwindows.sandbox = "mxc"gavemxc(the log saidMXC availability: trueon this build). The tool's reading of the configuration agreed each time. On thewindows-latestrunner (Windows Server 2025, 10.0.26100), where the log saidMXC availability: false,features.prefer_mxc = trueselected no sandbox (disabled, headerread-only),windows.sandbox = "mxc"was logged asmxcall the same, and the tool agreed with the log in each case. codex doctor(174 lines here, 165 on the runner) names no shell, PowerShell or bash.
The npm package @google/gemini-cli (bundle\gemini.js; engines.node >=20). test/e2e-gemini.test.mjs (7 tests) ran gemini -p ... --yolo --skip-trust --output-format json against a fake Gemini API (the requests of its utility router are answered with a document built from the schema they send); seven turns went in parallel, and the file took 15 seconds here:
- The shell. With no
pwsh.exeon PATH,run_shell_commandranC:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -NonInteractive -Command ...in Windows PowerShell 5.1; with a stand-inpwsh.exefirst on PATH,pwsh.exe -NoProfile -Command ...(no-NonInteractive);__PSLockdownPolicy=4in the environment of the process left the language mode atFullLanguage. The process also printed the file it was started from (Get-Process -Id $PID | Select-Object -ExpandProperty Path): a copy ofpowershell.exefirst on PATH was the file that ran, and apwsh.exein the project folder was not started (thepowershell.exeofSystem32ran). With the PowerShell folder off PATH, and apwsh.exein aProgramFilesfolder that PATH does not list, the tool answered{"error":"spawn powershell.exe ENOENT"}; the model got that, the answer was printed, and then, with Node 24.19.0, the process ended with a libuv assertion and exit code 3221226505 (in every run here; it ended with 0 when a PowerShell was found, and with 0 in both cases with Node 22.23.3 on thewindows-latestrunner). On that runner, which has PowerShell 7 inC:\Program Files\PowerShell\7, the other rows gave the same answers, and the row with the PowerShell folder off PATH still ended inspawn powershell.exe ENOENT: Gemini CLI does not look in Program Files. On PowerShell the tool refuses a command that holds a parenthesis outside quotes, such as(Get-Process -Id $PID).Pathor a method call like[Diagnostics.Process]::GetCurrentProcess(): it answers "Command injection detected: command substitution syntax ... On PowerShell, @() array subexpressions and $() subexpressions are also blocked" and runs nothing, which is why the test's command has none. - Hooks. The parent of a
BeforeToolorSessionStarthook waspowershell.exe -NoProfile -NonInteractive -Command "<command>; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }". A hook with&&did not run, and PowerShell's parser error ("not a valid statement separator") was printed as a warning while the tool ran. A hook that exited with 2 blocked the tool call ("Tool execution blocked: "); one that exited with 1 did not.test/windows.test.mjsshows why this differs from Codex:powershell.exe -Command "node -e process.exit(2)"exits with 1, and with the appended check with 2. - node-pty. The package lists
@lydell/node-ptyand@lydell/node-pty-win32-x64as optional dependencies, and the install hasconpty.node.
The npm package @github/copilot (npm-loader.js, which starts copilot-win32-x64\copilot.exe; the package has no engines field; copilot.exe unpacks the package it runs under %LOCALAPPDATA%\copilot\pkg\win32-x64\1.0.98). test/e2e-copilot.test.mjs (9 tests) ran copilot -p ... --allow-all-tools -s offline against a fake chat completions API:
- The shell. The
powershelltool printed its own command line:"powershell.exe" -NoProfile -NoLogo -NonInteractive -Command ...in Windows PowerShell 5.1 ($PSHOMEunderWindowsPowerShell\v1.0) with nopwsh.exeanywhere,"pwsh.exe" ...with a stand-in on PATH, andpowershell.exeagain when apwsh.exewas only in aProgramFilesfolder off PATH. The process also printed the file it was started from: a copy ofpowershell.exefirst on PATH was the file that ran, and apwsh.exein the project folder was not started (thepowershell.exeofSystem32ran). With neither on PATH the tool was still listed to the model and answered "PowerShell is not available. Tried "pwsh.exe", "powershell.exe". Install PowerShell from https://aka.ms/powershell ...", withspawn pwsh.exe ENOENTandspawn powershell.exe ENOENT; the same on thewindows-latestrunner, which has PowerShell 7 inC:\Program Files\PowerShell\7, so Copilot CLI does not look there either. - Hooks. The
powershellfield ran as"powershell.exe" -nop -nol -c "<command>"(pwsh.exewhen PATH had one);&&in it was a parse error of Windows PowerShell 5.1, the hook did not run, and Copilot CLI answered the tool call "Denied by preToolUse hook from ... (hook errored)"; so did a.ps1started with-ExecutionPolicy Restricted, and withBypassit ran. Abashfield ran asbash --norc --noprofile -c ...through a Git Bash first on PATH, and was denied the same way with no bash on PATH and with the WSL launcher (C:\Windows\System32\bash.exe) first.execwithargsstarted the program with no shell (the parent wascopilot.exe). A hook in the project's.claude\settings.jsonran through PowerShell withCLAUDE_PROJECT_DIRandCOPILOT_PROJECT_DIRset and$CLAUDE_PROJECT_DIRan empty PowerShell variable; the same hook in~\.claude\settings.jsondid not run; apowershellfield in.github\hooks\*.jsonran.
The Cursor CLI was not installed or run: it needs a login, and its installer (irm 'https://cursor.com/install?win32=true' | iex) downloads and runs a script, which this build does not do. What is checked is the launcher: the files of a Cursor CLI 2026.06.16 install that was on this machine (agent.cmd, the lines of cursor-agent.ps1 that pick a version folder, the name of the folder the installer made) were read, and test/cursor-files.mjs holds them as they were. agent.cmd starts %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "...\cursor-agent.ps1" %* after setlocal enabledelayedexpansion; the folder 2026.06.16-20-30-07-a07d3ac does not match the pattern ^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$ that the script uses, which PowerShell's own -match confirmed (test/windows.test.mjs). What the launcher then does was read from the script, not run. Everything about Cursor's hooks and its shell is documented, from the Cursor documentation and the forum threads, and marked so.
test/e2e-live.test.mjsruns the tool's owncheck --live(in a child process, behind the guard, with a PATH of the system folders) against each real binary and against all four: each reported "proved with the real ", the version the binary printed for itself was the one the report states, and a path that is not a file failed with exit code 1.test/windows.test.mjs(16 tests) reads the real Windows, read only, and cross-checks each answer another way:HKCU\Environmentthroughreg.exeagainst .NET's list of user variables;LongPathsEnabledand the ANSI code page against PowerShell's registry provider; the build, update revision and release of Windows against .NET and the registry; the execution policy of every scope, the code page,git configand Node against PowerShell,gitand the runner;wsl.exe -l -v(UTF-16) againstwsl.exe -l -q; that every Git Bash found starts; everybash.exeon PATH againstwhere.exe; each PowerShell edition's version, language mode and execution policy against what PowerShell says of itself; the PATH folders and program files of a command againstwhere.exe, and which of the same command in several folderscmd.exestarts first (folders in the order of PATH, files in the order ofPATHEXT:.batbefore.cmd) against the one the tool names; the Cursor version pattern against PowerShell's-matchon nine folder names; the version resource ofpowershell.exeandnode.exeagainstGet-Item; the Codex desktop app againstGet-AppxPackage; the eight kinds of bash syntax the tool names (&&,$VAR,/dev/null,export,[ -f x ],if ...; then, a here-document, a.shfile) against what the realpowershell.exe5.1 does with them; the exit codes of-Command; andcheck,agentsand--markdownrun in a throwaway world, which they leave as it was.- The install line.
npx -y github:nrzz/agent-windows-doctor#5d6350a --versionprintedagent-windows-doctor 1.0.0, and the line without a commit (npx -y github:nrzz/agent-windows-doctor explain codex.shell-path-ignored) printed the page, with npm 11.17.0, a throwaway npm cache and a throwaway home.checkwas not run through it: it reads the machine.
- 279 automated tests (
npm test, Node's own runner, no dependencies).node --testalso reports each of the 12 helper files undertest/as a passing test; those are not counted here (the runner's total was 291). On Windows 235 pass and 44 are skipped when no real binary is named: the skipped ones are the real-binary checks described above, which run only whenE2E_CLAUDE,E2E_CODEX,E2E_GEMINIorE2E_COPILOTnames a binary. On Linux and macOS 218 pass and 61 are skipped, because the tests that read the real Windows are skipped as well; everything else there runs against pretend Windows machines. The test that states these numbers fails if the README's are not the runner's, on each system. The test that states this number runs the suite a second time to count it: one run of the suite takes 35 seconds on the machine this was written on, andnpm test, with the count, 59. The tests cover:- Claude Code's shell: the 129 cases of the table against the prediction on a pretend machine, the settings files, Git Bash, the user environment, WSL (
claude-shell.test.mjs,claude-settings.test.mjs,gitbash.test.mjs,userenv.test.mjs,wsl.test.mjs,facts.test.mjs). - The inventory of binaries, launchers and shims, and versions read without running anything (
inventory.test.mjs), the machine and the PowerShell editions (machine.test.mjs), Codex on Windows (codex.test.mjs), the Cursor launcher, node-pty and the Node each package asks for (agentfacts.test.mjs), and the hooks (hooks.test.mjs). - The findings: each of the 77 ids is produced by some pretend machine, with the severity of its table and the basis of its page (
findings.test.mjs); the pages ofexplain(explain.test.mjs); the text, markdown and JSON reports, the cap of 12 findings and 500 characters, and that a secret in the environment is in none of them (report.test.mjs); the options, the exit codes and every usage error (cli.test.mjs). check --liveagainst stand-ins for the four binaries: the guard refuses a missing, empty, relative or outside folder variable and a proxy that is not the closed port, in every spelling; the fake APIs; what each run reads (live.test.mjs). The guard of the tests, the same for the real binaries (sandbox.test.mjs).- The real Windows, read only (
windows.test.mjs), and the real binaries (e2e-claude.test.mjs,e2e-codex.test.mjs,e2e-gemini.test.mjs,e2e-copilot.test.mjs,e2e-live.test.mjs). - The repository and the README (
repo.test.mjs,readme.test.mjs): no dependencies, who may start a program, open a connection or write a file, that no agent binary starts outside the guard, LF line endings, the community files and the workflows; the README's sections, usage block, numbers, table of findings, JSON shapes, examples and Files table.
- Claude Code's shell: the 129 cases of the table against the prediction on a pretend machine, the settings files, Git Bash, the user environment, WSL (
What was not put to a binary, and what the report says about it (each is marked documented or not verified where it comes up):
- Windows 10, Windows on ARM64, 32-bit Windows, and Windows 11 builds below 26100.9278. The runs were on Windows 11 on x64.
codex.mxc-unavailableis documented, not tried on a Windows 11 below the minimum build, because none was at hand (the runner's Windows Server 2025 showed the effect of an unavailable MXC, above). - Cursor. The Cursor CLI was never installed or run (a login is needed, and the installer downloads and runs a script). The launcher was read from disk and its pattern was held to PowerShell's
-match; the shell the CLI gives the model, the shell that runs its hooks, theMSYSTEMbehaviour, the byte order mark in a hook's input and the extensionless-launcher dialog are documented from the forum and the documentation. The Cursor editor is only found, with its file version. - Other installs. The agents were installed with npm (Claude Code also as the desktop app's bundle). The WinGet packages, the standalone Codex installer, the editor extensions' copies of Claude Code, the Microsoft Store package of Codex and the Cursor editor are found and listed, with a version where one can be read, and never run. A Microsoft Store alias of
pwshis modelled, not tried. - PowerShell 7 itself. This machine has none; the runs with
pwsh.exeused a copy ofpowershell.exeunder that name. What PowerShell 7 itself does (its&&, and the PowerShell 7 inC:\Program Filesthat Codex falls back to) was put to a binary only by thee2eworkflow onwindows-latest, which has it, and not here. - Cygwin and MSYS2. Git for Windows'
bash.exeand the WSL launcher are the bashes that are told apart; any otherbash.exeis "another bash". - The desktop apps and the editors. The shell that the Claude desktop app, the Codex desktop app, VS Code and JetBrains give the model is not checked; their binaries are found, and the environment they read (the user environment, once, at start) is compared with this process, not tried.
- Codex CLI. The
elevatedsandbox (its setup needs an administrator), any command run inside a sandbox, managed hooks (requirements.toml), the review of hooks in the CLI (/hooks, which needs the interactive CLI), project.codexlayers and their trust (read the way codex-config-doctor put them to the binary), and the WSL mode of the desktop app that reads the WindowsCODEX_HOME(codex.home-wsl, documented from the issue). - Gemini CLI under Constrained Language Mode (documented from the issue; a process cannot be put in the mode from its environment), the system settings file under
ProgramData\gemini-cli, and an install without the native half of node-pty. - Copilot CLI policy hooks (
ProgramData\GitHub\Copilot\policy.dand the registry) and the hooks that plugins ship (github/copilot-cli#3659). - Claude Code whether the PowerShell tool is on when Git Bash is found and the variable is not set (an account decides, and a fake API has no account:
checksays so and quotes the documentation), a Group Policy execution policy,LongPathsEnabledoff, a stale.git\index.lockleft by the Bash tool (claude.index-lock, not verified), what the Bash tool says when itsbash.execannot start, andclaude.launcher-first(not verified). - WSL. The WSL side of
checkis held to pretend machines only: the one distribution on this machine is Docker Desktop's, whichcheckcalls another tool's, and the workflows install none. - Other versions. The statements about an agent are about the versions named above.
e2e.ymlinstalls the newest release of each agent weekly and on every push, so a release that changes how an agent finds its shell shows up there, not here.
One copy of everything: nothing depends on another repository. The files marked "copied" come from the author's own repositories (MIT), and the CHANGELOG says so.
| Path | What it holds |
|---|---|
bin/agent-windows-doctor.mjs |
the command line entry |
src/cli.mjs |
the options, the help, the exit codes, the three commands |
src/check.mjs |
check: collects the facts of the Windows machine, or of the WSL side, and hands them to the findings |
src/findings.mjs |
the findings: their ids, severities, messages, effects and fixes |
src/explain.mjs, src/pages-claude.mjs, src/pages-others.mjs |
explain: the page of every id, each line marked verified, documented or not verified |
src/report.mjs |
the text, markdown and JSON reports |
src/inventory.mjs, src/versions.mjs |
every binary, launcher and shim of each agent, and which runs first; versions read without running the program |
src/shells.mjs |
the shell that Codex CLI, Gemini CLI, Copilot CLI and Cursor give the model, and how each finds it |
src/claude-shell.mjs, src/claude-settings.mjs, src/gitbash.mjs |
Claude Code's shell: its search for Git Bash and the rules of its PowerShell tool, the settings files that bear on them, where a Git Bash is (copied from claude-windows-doctor's shell.mjs, settings.mjs and gitbash.mjs) |
src/codexwin.mjs, src/toml.mjs |
Codex on Windows: CODEX_HOME, the config layers, the sandbox it selects and its log; the TOML reader that keeps the line of every key (copied from codex-config-doctor) |
src/agentfacts.mjs |
the Cursor launcher chain, the native half of Gemini CLI's node-pty, the Node each npm package asks for |
src/hooks.mjs |
the hook files of each agent, what starts each hook, and whether a command is a bash line |
src/machine.mjs, src/psprobe.mjs |
the Windows build and the MXC minimum, bash.exe on PATH, the PowerShell editions with their language mode and execution policy |
src/facts.mjs, src/userenv.mjs, src/wsl.mjs, src/winenv.mjs, src/winpath.mjs, src/jsonfile.mjs, src/sys.mjs |
Node and npm, long paths, core.autocrlf, the execution policy, the code page, .git\index.lock; the user environment against this process; WSL; the registry through reg.exe (the reading part only); Windows path rules that behave the same on every system; JSON read the way the agents read it; the machine as the checks see it, and the one place that starts a program for them (copied from claude-windows-doctor and claude-mcp-check) |
src/live.mjs |
check --live: the guard, the fake APIs on 127.0.0.1, the real binaries; the only code that opens a connection |
src/write.mjs |
the only code that writes: the throwaway folders of check --live |
src/version.mjs |
the version |
test/ |
npm test. The unit tests (*.test.mjs, one for each part of src/), repo.test.mjs (the shape of the repository: no dependencies, who may start a program, open a connection or write a file, LF line endings, the community files, the workflows) and readme.test.mjs (the README held to the code and to the runner). e2e-claude.test.mjs, e2e-codex.test.mjs, e2e-gemini.test.mjs, e2e-copilot.test.mjs and e2e-live.test.mjs put the claims to the real binaries when E2E_CLAUDE, E2E_CODEX, E2E_GEMINI or E2E_COPILOT names one; windows.test.mjs cross-checks what the tool reads on the real Windows. The helper files: helpers.mjs (the throwaway world, and the guard that points every home folder and every proxy of the test process away from the real ones), sandbox.mjs (the guard that runs before any binary starts, and the launcher), agents.mjs (the fake APIs and recipes of the four agents), record-hook.mjs (records what an agent sends a hook and what started it), machine.mjs and win-machine.mjs (a pretend machine, and a clean pretend Windows machine that a test spoils in one way), shell-table.mjs, table-lib.mjs and claude-claims.mjs (the 129 cases of Claude Code's shell, how a case becomes a pretend machine or real files, and the rows put to the binary), cursor-files.mjs (the Cursor CLI launcher files), fake-agent.mjs (a stand-in for an agent's binary), documents.mjs (one JSON document of every kind) |
.github/workflows/test.yml, .github/workflows/e2e.yml |
the suite on Windows, macOS and Linux with Node 20, 22 and 24 (and 18 on Linux); the real agents, installed from npm, on the three systems |
.github/ISSUE_TEMPLATE/, .github/PULL_REQUEST_TEMPLATE.md, .github/dependabot.yml |
the issue forms, the pull request template, the weekly update of the actions |
CONTRIBUTING.md, CHANGELOG.md, ROADMAP.md, SECURITY.md, SUPPORT.md, CODE_OF_CONDUCT.md, LICENSE |
the community files and the licence |
Bug reports, a wrong prediction (with the output of check --json, and of check --live if you can run it) and pull requests are welcome; read CONTRIBUTING.md first (the house rules, the guard that every real-binary run goes through, how the tests are laid out). The ideas that are open are in ROADMAP.md, and the good first issue is #1. Changes are listed in CHANGELOG.md; the Code of Conduct applies, and security problems go to SECURITY.md, not to an issue.
The Claude Code toolkit has nineteen tools for Claude Code, and the two closest to this one are claude-windows-doctor, which goes deeper on Claude Code alone, and everyhook, which writes one hook for every agent.
MIT. See LICENSE.