PreToolUse hook: exit 0 but empty stdout under concurrent tool calls (failClosed blocks)

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

With failClosed: true on a user PreToolUse hook, concurrent Agent tool calls intermittently return exit code 0 with empty stdout. Cursor then reports the hook “returned no output” and blocks the tool (e.g. Read/Write). The same hook command invoked outside Cursor with piped stdin (including 20 parallel jobs) always returns {“permission”:“allow”}. This is not a policy deny.

Steps to Reproduce

  1. Configure a single user PreToolUse hook in ~/.cursor/hooks.json with failClosed: true, e.g.
    command: C:/Users//AppData/Roaming/npm/cc-safety-net.cmd hook --cursor
    timeout: 30
    failClosed: true
  2. In Agent chat, issue two tool calls in one turn (e.g. parallel Read of two different files).
  3. Observe: one call succeeds; the other is blocked with “hook … returned no output”.
  4. Check Output → Hooks (or %APPDATA%\Cursor\logs...\cursor.hooks.workspaceId-*.log): the failing call shows exit code 0 and OUTPUT: (empty) after ~1–2s (not a timeout).

Expected Behavior

Each PreToolUse invocation should receive the hook process’s JSON on stdout (or a real non-zero exit / deny). Concurrent tool calls should not drop stdout while still reporting exit code 0.

Operating System

Windows 10/11

Version Information

3.24.10

For AI issues: add Request ID with privacy disabled

N/A — Privacy Mode enabled (Request ID not useful without Share Data)

Additional Information

Privacy Mode is enabled, so a Request ID is not useful for backend lookup.

Evidence is local Hooks channel / on-disk log (2026-10-09T13:55:28Z):

Parallel Reads ~0.6s apart:

  • Read .cursor/BUGBOT.md → Command exit code 0 (1328ms), OUTPUT: (empty), “Hook 1 produced no output”
  • Read .github/copilot-instructions.md → exit code 0 (1161ms), OUTPUT: {“permission”:“allow”}

Same pattern repeated at 13:55:59Z. Solo Write of the same path shortly after succeeded.

Control: outside Cursor, 20 concurrent invocations of the identical .cmd with JSON on stdin → 0/20 empty.

Note: managed npx -y cc-safety-net hook --cursor was abandoned earlier because npm can prepend warnings to stdout and break JSON parse; absolute .cmd was intentional.

On-disk Hooks log path pattern:
%APPDATA%\Cursor\logs<session>\window*wb*\output\cursor.hooks.workspaceId-.log

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey @my216, thanks for the report and for the control test outside Cursor.

This is a known issue we’re already tracking, and I’ve added your report to it. When a hook process exits right after writing its output, Cursor can occasionally treat it as finished before it has read all of stdout. That shows up as exit code 0 with empty output, and with failClosed: true the tool call gets blocked. Parallel tool calls widen the timing window, which matches what you’re seeing.

In the meantime, giving the hook a short pause after it writes its output should make this much rarer (it won’t fully eliminate it). On Windows you can do that directly in hooks.json by appending a sleep to the command:

C:/Users/<user>/AppData/Roaming/npm/cc-safety-net.cmd hook --cursor; Start-Sleep -Milliseconds 150

Let me know if that cuts down the blocked calls for you.

Thanks @Colin . That has solved the problem for me.