Race condition silently disables hooks that exit quickly

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

A race condition causes responses from fast hooks to be ignored. The hook exits successfully, but Cursor receives partial or empty stdout, so permissions
and input changes are not applied.

Steps to Reproduce

  1. Register a fast Go, Rust, or C preToolUse hook for Shell that immediately prints {"permission":"deny","user_message":"blocked"} and exits 0.
  2. Have the Agent issue many separate Shell tool calls concurrently. Do not use one Shell tool call that starts multiple processes.
  3. Check the Hooks log.

Some runs report exit code: 0 with partial or empty output, and the denied command is allowed.

Expected Behavior

Cursor should capture all output from every hook process and apply each valid response, regardless of execution speed or concurrent hook activity.

Operating System

Linux

Version Information

Version: 3.11.19
VS Code Extension API: 1.125.0
Commit: bf249e6efb5b097f23d7e21d7283429f0760b740
Date: 2026-07-12T21:39:24.175Z
Layout: IDE
Build Type: Stable
Release Track: Default
Electron: 40.10.3
Chromium: 144.0.7559.236
Node.js: 24.15.0
V8: 14.4.258.32-electron.0
xterm.js: 6.1.0-beta.256
OS: Linux x64 6.17.0-122035-tuxedo

Additional Information

A similar issue was confirmed by Cursor in March 2026 and is now closed:

That report may describe the same underlying bug. This still reproduces on Linux in Cursor 3.11.19.

The last good official package I verified was cursor_1.7.54_amd64.deb (1.7.54-1761074318). The first affected package I verified was cursor_2.0.4_amd64.deb (2.0.4-1761336133).

In Cursor 3.11.19, the relevant code appears to be in:

/usr/share/cursor/resources/app/out/vs/workbench/api/node/extensionHostProcess.js

This path is used by both Desktop and Glass.

Formatted excerpt:

for await (const event of executor.execute(...)) {
  if (event.type === "stdout")
    stdout += event.data.toString("utf8");
  else if (event.type === "stderr")
    stderr += event.data.toString("utf8");
  else if (event.type === "exit")
    return { stdout, stderr, exitCode: event.code ?? 1 };
}

I may be missing context, but process exit and pipe delivery are separate events. Cursor appears to process the exit while stdout or stderr events are still pending.

I extracted this control flow into a standalone contrived test. It reproduced the same result: exit code 0 with incomplete stdout. That test only validates the suspected race in the bundled code; the Cursor reproduction is described separately above.

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey @ragnoroct
This is a real bug on our side, not your setup.

For fast-exiting command hooks, the process can be treated as finished before all of its stdout has been read, so Cursor ends up with partial or empty output. When that hits a preToolUse hook returning {"permission":"deny"}, the deny gets dropped and the command runs. Concurrent Shell calls widen the window. I’ve reported this to the team and attached your report.

While a fix is worked on, two things:

  1. To shrink the race, have the hook flush stdout and wait a brief moment (around 50ms) before exiting, instead of exiting instantly. It’s not bulletproof, but it gives a fast compiled hook’s output time to be captured.
  2. Since this is a timing issue, please don’t rely on a deny hook alone as a hard block for risky commands right now, since a dropped deny means the command can still run. Cursor’s built-in Auto-run command controls are a more reliable layer for hard enforcement until this is fixed.

I’ll update here once a fix ships in a release.