Hooks never run in cursor-agent CLI on Windows + Git Bash — the failure becomes a silent hard deny (still repro on 2026.09.02)

Where does the bug appear (feature/product)?

Cursor CLI

Describe the Bug

Hooks configured in .cursor/hooks.json never run in the CLI on Windows when Git Bash is
present. Instead of running, the tool call is reported to the agent as “Hook blocked”.

The command Cursor builds is PowerShell, but it is handed to bash:

--: eval: line 1: syntax error near unexpected token `&'
`$OutputEncoding = [System.Text.Encoding]::UTF8;
Get-Content -LiteralPath '...\cursor-hooks-XXXX\payload.json' -Raw |
& { $input | .cursor/hooks/observe.cmd }'

This is the same root cause confirmed in January 2026 (“Hooks use PowerShell syntax to decode
base64, which isn’t compatible with Bash”). That thread has since auto-closed with no fix
announced, and similar reports have kept appearing for eight months, so I am filing a fresh
one with CLI-specific detail.

The important part is not just “the hook does not fire”. A hook written to OBSERVE or ALLOW
silently becomes a hard DENY. And when this is fixed, calls that are currently blocked will
start passing – a silent reversal, not an error. Anyone whose setup currently looks safe
because hooks happen to fail closed will quietly lose that protection.

Steps to Reproduce

  1. On Windows with Git Bash installed (SHELL points at Git Bash), create a workspace.

  2. Create WORKSPACE/.cursor/hooks.json:

    { "version": 1,
      "hooks": {
        "preToolUse":     [ { "command": ".cursor/hooks/observe.cmd", "timeout": 30 } ],
        "beforeReadFile": [ { "command": ".cursor/hooks/observe.cmd", "timeout": 30 } ] } }
    
  3. Create WORKSPACE/.cursor/hooks/observe.cmd that appends its stdin to a log file and
    prints {"permission":"allow"} on stdout. (Any hook works – the script is never reached.)

  4. Put any file in the workspace, e.g. target.py.

  5. Run:

    cursor-agent -p --output-format text --model MODEL --trust "Read target.py"
    
  6. Observe: the hook’s log file is EMPTY (the hook never started), and the agent reports the
    Read as “Hook blocked” with the bash syntax error shown above.

Control: delete hooks.json and re-run – the file is read normally.

Expected Behavior

The hook should run: the JSON payload should reach the hook script on stdin, and the hook’s
{"permission": "allow"} should let the tool call proceed.

Failing that, a hook that cannot be launched should be reported as a hook ERROR distinct from
a policy DENY, so that “the hook is broken” is not silently indistinguishable from “the hook
decided to block”.

Operating System

Windows 10/11

Version Information

CLI:
CLI Version 2026.09.02-c22c1a3
Latest 2026.09.02-c22c1a3 (up to date)
Model Cursor Grok 4.6 High Fast
Subscription Tier Team
OS win32 (x64)
Terminal unknown
Shell c:\program files\git\bin\bash.exe

For AI issues: which model did you use?

Model name: cursor-grok-4.6-medium (also reproduced with the account default, Cursor Grok 4.6 High Fast). The model is not relevant – the failure is in the hook invocation, before the model’s tool call is executed.

For AI issues: add Request ID with privacy disabled

Not available – privacy mode is enabled on this account. Happy to reproduce with privacy disabled and provide a Request ID if that would help.

Additional Information

Workarounds that do NOT help

All tried on the CLI, identical failure each time:

  • .sh and .cmd hook scripts
  • absolute path in “command”
  • cmd /c "<absolute path>"
  • explicit interpreter (uv run --no-project python <absolute path>)
  • unsetting SHELL

Unsetting SHELL DOES change what agent about reports
(c:\program files\git\bin\bash.exe → cmd), but the hook invocation is unaffected. So the
hook’s shell selection appears to come from somewhere other than what about reports –
that may be a useful pointer.

Prior reports of the same family

  • Project-level hooks fail on Windows with Git Bash due to PowerShell injection (148131, closed)
  • Hook Processing Fails on Windows with Git Bash Terminal (146808)
  • Project-Level Hooks Fail to Execute on Windows (144940)
  • Cursor Hooks On Windows (140293)
  • sessionStart plugin hook produces no output on Windows (152629)
  • The cursor hooks did not execute as expected (155711)
  • Tool Calls / Commands Hanging in Git Bash (156356)

Why this blocks us

We need to bound what an agent may read. permissions covers
Shell / Read / Write / WebFetch / Mcp, so Grep and Glob do not appear to be restrictable
there – we tried Grep(**) / Glob(**), a "*" catch-all, dropping --trust, and
approvalMode: “default”, and in every case the agent still read a file outside the workspace
by absolute path. Hooks look like the only remaining lever, and we verified preToolUse does
match Read, Grep and Glob – which is what makes this bug the blocker for us.

If there is an existing way to restrict Grep/Glob that we missed, we would be glad to be
corrected – that would remove our dependency on hooks entirely.

Related known issues on Windows (same family, worth fixing together)

  • The hook stdin JSON is prefixed with a UTF-8 BOM, so a plain JSON.parse throws – reported
    to make security hooks silently degrade to “allow”.
  • Extensionless hook scripts are treated as a file-open action rather than executed.

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey @hanyu_masashi
This isn’t something you misconfigured, and it’s an issue we’re tracking.

The real trigger is the MSYSTEM env var that Git Bash sets (not SHELL - which is why unsetting it didn’t help, and MSYSTEM can’t be cleared from inside Git Bash). Workaround:

  1. Open PowerShell or cmd directly from the Start menu, not from Git Bash or anything it launched.
  2. Check it’s clean: echo $env:MSYSTEM should print nothing. If it prints MINGW64, remove it under System Properties → Environment Variables.
  3. cursor-agent about should now show PowerShell/cmd. Re-run your Read target.py test - the hook should fire and your allow/deny logic should apply.

Once hooks run under PowerShell: strip a leading \uFEFF (BOM) before JSON.parse, and keep timeout generous (PowerShell adds a little startup time per call).

On Grep/Glob: permissions has no entries for those today, so preToolUse/beforeReadFile hooks are the supported way to fence them, which this gets you back to. Same root cause is tracked in this thread. No timeline yet, but I’ll post here when there’s an update. Does launching from a clean PowerShell window get your hook firing?

I was trying same with cursor agent in ACP format. none of the hook worked for me for analytics.
cursor CLI version: 2026.09.15-d2fe57e
I was working on 2 tools afterAgentResponse and stop but none of them fired.
Step to reproduce:

  • start cursor cli in ACP mode
  • have stop and afterAgentResponse in workspace where cursor cli is invoked with event logs getting printed.
  • give prompt and wait for turn to end.
  • no event logs getting printed.

Thanks @mohitjain for the MSYSTEM pointer — I hadn’t seen that reply until now. Wanted to
follow up with what I found when I isolated the variables, since the diagnosis doesn’t
quite match what I measured.

Still reproducing on 2026.09.18-9a7762b (current release as of 2026-09-20).

MSYSTEM is not actually the determining factor. I ran a 2x2:

bash.exe anywhere in the process ancestry MSYSTEM Result
yes (default) set (default) fails — same syntax error as before
yes explicitly unset (env -u MSYSTEM) still fails
no (launched via WMI Win32_Process.Create) unset (WMI service’s default env) succeeds — hook fires, deny is honored
no (same WMI launch) explicitly set to MINGW64 still succeeds

So clearing MSYSTEM alone, while the calling process is still a bash descendant, does
nothing. What actually matters is whether bash.exe is anywhere in the process ancestry at
all. I confirmed this a second way — launching via a Scheduled Task running interactively
as the logged-on user (/it, closer to a real PowerShell-prompt launch than WMI’s Session
0 context) — and got the same result: hook fires correctly, deny works, regardless of
MSYSTEM.

Once the hook actually runs, it behaves correctly: I set up a beforeShellExecution hook
returning {"permission":"deny", ...} and the shell call was properly blocked, with my
hook’s own user_message echoed back to the agent. So this isn’t a deeper bug in
permission handling — it really is just the wrapper string ($OutputEncoding = ...; & { $input | <hook command> } ) being handed to the wrong interpreter when a bash ancestor
is present.

Also confirmed something not in the original report: cursor-agent auto-discovers hooks from
~/.claude/settings.json (Claude Code’s own config) when no .cursor/hooks.json is
present in the workspace — the denial message named our actual Claude Code hooks by name.
Nice bit of interop when it isn’t hitting this bug.

Practical takeaway for anyone hitting this: if you can arrange for cursor-agent to be
launched without a bash/Git-Bash ancestor in its process tree (a scheduled task, a
shortcut, invoking it directly from a PowerShell/cmd session that wasn’t itself opened
from Git Bash), hooks work today. The trap is anything that spawns cursor-agent as a child
of bash — including, ironically, opening a Git Bash tab and typing the command directly.

Happy to share the exact repro scripts if useful for triage.

@hanyu_masashi, apologies for the earlier MSYSTEM steer. The trigger is whether Git Bash is anywhere in the process ancestry that launches cursor-agent, not MSYSTEM itself. On Windows the hook command is built as PowerShell but handed to the auto-detected shell, and Git Bash gets picked whenever its environment is inherited, which turns an allow/observe hook into a “Hook blocked”. Not intended, nothing you misconfigured, and an issue we’re tracking. I’ll post here when there’s an update.

Your workaround is the right one: launch cursor-agent with no Git Bash ancestor in its process tree (a PowerShell or cmd window not opened from Git Bash, or a shortcut / scheduled task). Hooks then fire and allow/deny works normally.

@prashant_kanak, yours may be the same cause. What OS are you on, and are you launching cursor-agent from Git Bash (or anything started by it)? On Windows with a Git Bash ancestor, try a clean PowerShell/cmd window and tell me if your event logs start appearing.

I am working on mac OS and I am using terminal to invoke agent.

Hey @prashant_kanak, thanks for flagging this. This thread is specific to Windows plus Git Bash, where the hook command gets built for PowerShell and then handed to bash, so it doesn’t apply to your macOS setup. Your hooks not firing in ACP mode looks like a separate issue. Could you open a new thread for it? Please include your CLI version, the ACP client and the exact command and working directory it launches from, and your .cursor/hooks.json, and we’ll dig in from there.