Cursor-agent (Windows): imported Claude Code hooks are composed as PowerShell but executed with bash, silently blocking every tool call

Where does the bug appear (feature/product)?

Cursor CLI

Describe the Bug

cursor-agent (Windows): imported Claude Code hooks are composed as PowerShell but executed with bash, silently blocking every tool call

Describe the Bug

On Windows, Cursor imports hooks from Claude Code’s config when no Cursor hook
config exists. It then wraps each imported hook command in a PowerShell
preamble, but evaluates the resulting string with bash. Every imported hook
therefore dies on a bash syntax error — and because a hook that errors blocks
the tool call, cursor-agent cannot edit files or run shell commands at all.

It still exits 0 and emits a clean result event, so in print mode the
failure is completely silent. The agent reads, reasons, and reports success
while every action it took was rejected.

Steps to Reproduce

  1. Windows 11 with Claude Code installed and at least one PreToolUse hook in
    ~/.claude/settings.json.
  2. No ~/.cursor/hooks.json, no project-level .cursor/hooks.json.
  3. In an empty git repo:
cursor-agent -p "Create a file named hi.txt containing hello. Nothing else." `
  --output-format stream-json --trust --force --model auto > out.jsonl
Test-Path hi.txt

Expected Behavior

hi.txt is created.

Actual Behavior

hi.txt is never created. Exit code is 0, stderr is empty, and the stream
contains tool_call/completed entries carrying rejections — both
editToolCall and shellToolCall:

"rejected": {
  "reason": "Hook blocked with message: --: eval: line 1: syntax error near unexpected token `&'
    --: eval: line 1: `$OutputEncoding = [System.Text.Encoding]::UTF8; Get-Content -LiteralPath
    'C:\\Users\\<user>\\AppData\\Local\\Temp\\cursor-hooks-GwQsZg\\payload.json' -Raw
    | & { $input | npx -y context-mode hook cursor pretooluse }'

    Agent note: Do not suggest workarounds to the blocked tool."
}

The command being evaluated is PowerShell ($OutputEncoding, Get-Content -LiteralPath, | & { $input | ... }), and the error is bash’s
(eval: line 1: syntax error near unexpected token). The cursor-hooks-XXXXXX
temp directory in that path is created by Cursor itself, so the PowerShell
wrapper is Cursor’s own composition — not anything the user wrote.

Why the hooks are Claude Code’s

Nothing was ever registered on the Cursor side. Cursor’s hook log states the
resolution order and the outcome:

User config path:        c:\Users\<user>\.cursor\hooks.json
Claude user config path: c:\Users\<user>\.claude\settings.json
No user hooks configuration found      <- no Cursor hook config exists
Loaded Claude user hooks               <- Claude Code's are adopted instead

(%APPDATA%\Cursor\logs\<timestamp>\window1_wb1\output_<timestamp>\cursor.hooks.workspaceId-*.log)

cursor-agent does the same — its bundle carries claudeUserHooks,
claudeProjectHooks and claudeProjectLocalHooks.

The two hooks blocking everything on this machine were rtk hook claude (a
PreToolUse entry in ~/.claude/settings.json) and
npx -y context-mode hook cursor pretooluse (contributed by a Claude Code
plugin). Both are perfectly valid Claude Code hooks. Neither was written for
Cursor.

Impact

Any Windows user with Claude Code hooks configured has a cursor-agent that
can read and reason but silently cannot act — with no error, no non-zero exit,
and nothing in stderr. In an automated harness this is worse than a crash: the
agent step “succeeds” and the next step fails for an unrelated-looking reason
(nothing to commit, working tree clean). It took two days to trace, and the
symptom points away from the cause at every step.

There is also no obvious off-switch: cursor-agent has no
CLAUDE_CONFIG_DIR support, and I could find no setting to disable the Claude
hook import.

Suggested Fix

  1. Execute the PowerShell-composed command with PowerShell, or compose a
    bash-compatible command when executing with bash.
  2. Separately: a hook that fails to execute is arguably different from a hook
    that denies a tool call. Right now a broken hook silently becomes a
    full-deny. A hook that cannot run at all could be surfaced as an error
    rather than as a rejection with "Do not suggest workarounds to the blocked tool."
  3. A documented way to opt out of importing Claude Code hooks would help
    regardless.

Workaround (for anyone who finds this)

Give cursor-agent a home directory containing your real .cursor and no
.claude:

$fh = "$env:TEMP\cursor-clean-home"
New-Item -ItemType Directory -Force $fh | Out-Null
cmd /c mklink /J "$fh\.cursor" "$env:USERPROFILE\.cursor"   # no elevation needed
$env:USERPROFILE = $fh; $env:HOME = $fh

The junction keeps authentication working. Verified: 3 rejections and no file
written → 0 rejections and the file created.

:warning: If you later delete that scratch directory, remove the junction with
cmd /c rmdir "$fh\.cursor" first — a recursive delete that follows junctions
would walk into your real Cursor config.

Environment

  • OS: Windows 11 Enterprise 10.0.26200
  • cursor-agent: 2026.08.11-e8db854 (also reproduced on 2026.08.04-aaa8809)
  • Flags: -p --output-format stream-json --trust --force --model auto
  • Config: approvalMode: allowlist, sandbox.mode: disabled
  • Not shell-dependent: reproduced from PowerShell with SHELL not exported,
    and with a clean Windows PATH. The launching shell makes no difference — I
    chased that first and it was a dead end.

Does this stop you from using Cursor?

Yes for headless/automated use on Windows. Interactive use is affected too, but
there a user notices the rejections; in print mode nothing surfaces at all.

Steps to Reproduce

  1. Windows 11 with Claude Code installed and at least one PreToolUse hook in
    ~/.claude/settings.json.
  2. No ~/.cursor/hooks.json, no project-level .cursor/hooks.json.
  3. In an empty git repo:
cursor-agent -p "Create a file named hi.txt containing hello. Nothing else." `
  --output-format stream-json --trust --force --model auto > out.jsonl
Test-Path hi.txt

Expected Behavior

hi.txt is created.

Operating System

Windows 10/11

Version Information

  • OS: Windows 11 Enterprise 10.0.26200
  • cursor-agent: 2026.08.11-e8db854 (also reproduced on 2026.08.04-aaa8809)
  • Flags: -p --output-format stream-json --trust --force --model auto
  • Config: approvalMode: allowlist, sandbox.mode: disabled
  • Not shell-dependent: reproduced from PowerShell with SHELL not exported,
    and with a clean Windows PATH. The launching shell makes no difference — I
    chased that first and it was a dead end.

Does this stop you from using Cursor

Yes - Cursor is unusable

Hey, thanks for the report. It’s one of the most detailed ones I’ve seen. Your diagnosis is correct, and we reproduced it: on Windows, the hook command is built as PowerShell, but it gets executed through bash when the environment has Git Bash markers. Bash crashes on the syntax, exit 2 matches the block code, so you get a quiet full-deny of all tool calls in print mode. I’ve passed this to the team.

For now, interim options:

  • Your junction trick with a clean HOME and USERPROFILE (no .claude, with a junction pointing to the real .cursor) is a valid workaround, keep using it for headless.
  • As an alternative, if your persistent environment has Git Bash markers like MSYSTEM, EXEPATH, or a global SHELL pointing to git-bash, removing them may force the CLI to pick PowerShell for execution, and imported hooks should run correctly.

On your specific points:

I can’t share a timeline for the main issue yet, but I’ll reply here when there’s an update. Let me know if the workaround doesn’t work in your harness.

Update: isolated the trigger — it’s MSYSTEM

Follow-up to the report above, with a much smaller reproduction and one
correction.

The hook block only happens when MSYSTEM is set in the environment. That
variable is exported by Git Bash / MSYS2 for every process it spawns. With it
set, Cursor executes its PowerShell-composed hook command with bash; without
it, the very same hooks run fine.

Isolated on one machine, same cursor-agent 2026.08.11-e8db854, same
~/.claude hooks, runs minutes apart. Each run starts from a fresh default
(registry-derived) environment plus exactly one addition:

Environment hi.txt created Hook blocked occurrences
clean default environment yes 0
clean + MSYSTEM=MINGW64 no 5
clean + Git Bash \usr\bin on PATH (no MSYSTEM) yes 0

So it is not PATH, and it is not the mere availability of bash — it is
MSYSTEM alone.

Minimal repro on any Windows machine with Claude Code hooks configured in
~/.claude/settings.json:

set MSYSTEM=MINGW64
cursor-agent -p "Create a file named hi.txt containing hello. Nothing else." --output-format stream-json --trust --force --model auto

hi.txt is not created, the stream carries "rejected" entries with the bash
syntax error from the original post, and the exit code is still 0.

Two things that make this worse than it first looks:

  1. MSYSTEM cannot be unset from inside Git Bash. The MSYS runtime injects
    it into the environment block of every child process — both env -u MSYSTEM
    and unset MSYSTEM still leave a native Windows child seeing
    MSYSTEM=MINGW64. So a user whose tooling launches cursor-agent from a
    Git Bash-parented process cannot opt out at all, short of relocating
    HOME/USERPROFILE away from ~/.claude.

  2. It reaches far past interactive use. Anything descended from Git Bash
    inherits it: shell scripts, test harnesses, and any long-running daemon or
    agent started from an MSYS shell. The same cursor-agent invoked from
    PowerShell, cmd, or a Windows Scheduled Task works perfectly — which is
    what makes it look intermittent.

Correction to the original post: having Claude Code hooks configured is
necessary but not sufficientMSYSTEM must also be set. On a stock Windows
shell the report will not reproduce, which is likely why it appears
environment-specific.

Hey, great follow-up. Narrowing it down to MSYSTEM really helps, thanks. This is super useful because it confirms it’s not about PATH or bash being present, it’s specifically that MSYSTEM makes the CLI pick bash to run a PowerShell-composed hook command. I added your comparison table, minimal repro, and the correction that Claude hooks are necessary but not sufficient to what I already sent to the team.

Also really valuable note that you can’t clear MSYSTEM from inside Git Bash. MSYS re-injects it into child processes, so unset or env -u won’t help for a native child. That makes the situation for a Git Bash-parented harness worse than it looks. There’s basically no opt-out except relocating HOME and USERPROFILE. I called that out too.

Your junction workaround with a clean HOME and USERPROFILE is still solid for headless runs, stick with it for now. I can’t share a timeline for the main issue yet, but I’ll post here when there’s an update. For an opt-out from importing third-party configs, keep an eye on the thread I linked above, updates will be posted there.