Agent Window steals focus and opens file tabs while typing follow-up promptsCategory

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

When I submit a prompt in the Agent Window and the agent begins working, it opens tabs for every single file it reads, reviews, or modifies. Instead of keeping these file operations hidden or in the background, Cursor forces active UI focus onto each newly opened file tab.

This behavior starts within 5 seconds of the prompt being sent to the agent and completely breaks my workflow when I try to type a subsequent command or follow-up prompt while it is running. The background file activity constantly pulls focus away from my input text box. This problem exacerbates severely when running multiple agents across multiple repositories concurrently, resulting in a constant loop of being interrupted mid-type, manually closing unwanted file tabs, and forcing focus back to the chat box.

As soon as all the running agents completely finish their tasks and stop doing work, this auto-focus behavior finally stops, confirming that the typing interruption is directly tied to active agent file tracking.

I have already tried disabling Agent Autocomplete and removing Tab keybindings, but this is a deep window focus/file tracking layout bug.

Steps to Reproduce

  1. Open the separate Agent Window on Windows 10 Pro.

  2. Submit a prompt that requires the agent to inspect or modify codebase files.

  3. Within 5 seconds of sending that prompt (as soon as the agent begins processing), click into the prompt input box and try typing a new command or follow-up text.

  4. As the agent scans the repo, it automatically opens a file tab and rips user focus completely away from your typing cursor.

  5. Once all background agent tasks stop completely, note that your typing focus is no longer stolen.

Expected Behavior

The agent should inspect and process workspace files silently in the background, or at least open tabs without stealing focus. The user’s active keyboard focus should stay locked inside the text input box so prompts can be typed out fully without interruption, regardless of whether background agents are running or finished.

Operating System

Windows 10/11

Version Information

Version 3.14.7
OS: Windows 10 Pro x64
UI: Agent Window

For AI issues: which model did you use?

Grok 4.5, Gemini 3.6

Additional Information

This issue makes multitasking with multiple agents or working across multiple repositories highly frustrating. I have to repeatedly fight the editor layout to finish typing a single command until the background queue clears.

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey there, I tried to reproduce this on Windows but was unsuccessful.

Could you share a short screen recording of it happening? Ideal if it shows: (1) you typing in the Agents Window follow-up box, (2) agents actively editing, and (3) which tabs/panels grab focus (Agents Window file pane vs the classic Editor/IDE). If you can also note roughly how many agents were running when it hit, that helps.

Once we can see the exact layout, we can dig in further.

I am also having this issue with Cursor IDE. Whenever I give an agent a task and it begins working, a new editor tab opens automatically. Even if I minimize the IDE window, the IDE automatically pops up. and interrupts whatever I was doing. In my case, the focus-stealing seem to be related only to GSD (git ship done) and Graphify hooks where the hooks spawn real powershell.exe / bash console processes on every fire. I am still trying to trouble shoot myself, but maybe the issue is Cursor’s hook spawn path.

Bug report from Claude. I am a vibe coder - so not really sure what it means, but I think i fixed my issue.

Cursor on Windows: third-party hook execution — focus theft and a Windows-only input channel

Filed: 2026-08-12
Cursor version: 3.15.6
OS: Windows 11 (build 26200), default console host is Windows Terminal
Agent: Cursor’s own agent (Grok 4.5)
Control: the Claude Code extension running in the same IDE, on the same machine, with the same hook configuration, does not exhibit either issue

Two separate defects are described. They were found while investigating the first; the
second is arguably more serious because it is silent.

Paths in this report are redacted as C:/Users/<user>/... and the project is <project>.


Issue 1 — Hook processes appear to be spawned without CREATE_NO_WINDOW, stealing OS focus

Symptom

With Cursor minimized and another application in the foreground, running the agent would
intermittently pull focus away. The interruption correlated with agent tool calls that
trigger hooks, not with agent activity in general. It did not occur with the Claude Code
extension in the same IDE running the same hooks.

Source-level evidence

All from resources/app/out in the installed build.

1. Cursor executes third-party Claude hook configuration. In the same service object
that loads its own hooks.json:

joinPath(e, ".cursor", "hooks.json"),
this.claudeUserConfigUri          = joinPath(e, ".claude", "settings.json"),
this.claudeProjectConfigUris      = e.map(t => joinPath(t.uri, ".claude", "settings.json")),
this.claudeProjectLocalConfigUris = ...settings.local.json
this.logger.logInfo(`Claude user config path: ${this.claudeUserConfigUri}`)

This is cursorHooksService (134 references in the bundle). Note that no
.cursor/hooks.json exists on this machine — the hooks being run come entirely from
.claude/settings.json.

2. Hooks dispatch through the general shell service, not a hardened path.
_executeCommandHookScript ends at:

this.shellExecService.executeHookDirect(cmd, cwd, isWindows, env, timeout, payload)

3. windowsHide appears in exactly one file across all of resources/app/out:
main.js (the Electron main process).
The workbench and extension-host bundles that
execute hooks never set it. A console-subsystem child spawned from a parent without an
inherited console gets a brand-new visible console window on Windows.

This is the single most checkable line in this report: grep your own build.

Independent measurement on this machine

To characterise what a naive spawn produces, a Node parent was launched with
{detached: true, stdio: 'ignore'} so it had no console of its own (approximating a
spawn from a GUI host), and it then spawned powershell.exe with windowsHide: false.
A Win32 EnumWindows watcher polling every 15ms recorded window creation.

Result: a visible CASCADIA_HOSTING_WINDOW_CLASS window (“Terminal”, retitled to the
executable path, then to “Windows PowerShell”) appeared every time.

Notable for anyone proposing -WindowStyle Hidden as a mitigation: it does not work.
The same A/B with powershell -NoProfile -WindowStyle Hidden -File script.ps1 produced a
visible Terminal window at equivalent timings. On Windows 11 the console host is Windows
Terminal, and PowerShell’s -WindowStyle calls ShowWindow on GetConsoleWindow(),
which under ConPTY is the hidden PseudoConsoleWindow, not the Terminal window. It hides
the wrong window. CREATE_NO_WINDOW on the spawn is the only thing that prevents it.

(Related trap, measured separately: applying -WindowStyle Hidden to a nested PowerShell
call that inherits its parent’s console calls ShowWindow(SW_HIDE) on the caller’s
window and never restores it. Any launcher that “helpfully” injects that flag would hide
the user’s own terminal.)

Current status — important caveat

The symptom is not currently reproducing, after two local changes were made before this
investigation began:

  1. Four .sh hooks (which launched Git Bash) were rewritten as Node .js hooks.
  2. -WindowStyle Hidden was added to the remaining PowerShell hook commands.

Three subsequent dense bursts of agent activity — many Reads, Globs, and shell commands,
all of which fire hooks — produced zero console windows and zero focus interruptions.

Given measurement (2) above, change #2 cannot be what fixed it, which points at #1 (Git
Bash / MSYS on the hot path). But this has not been isolated with a controlled A/B, so the
attribution is stated as unproven. The missing CREATE_NO_WINDOW in the spawn path is
still present in the code regardless
, and any user whose hooks invoke a console program
directly remains exposed.

Requested fix

Spawn hook processes with shell: false, windowsHide: true (CREATE_NO_WINDOW), and
piped stdio. Resolve the executable explicitly rather than relying on file association —
in particular, a bare .sh path should become [bash.exe, scriptPath, ...args], never an
“open this file” call. If a shell is genuinely required (pipes, redirects, &&), use
cmd.exe /d /s /c <command> with windowsHide: true on that cmd, rather than Node’s
shell: true. Do not activate or foreground the IDE window on hook start or completion.


Issue 2 — On Windows, hook input is delivered by temp file instead of stdin, silently breaking hooks that read stdin

Source-level evidence

In _executeCommandHookScript:

const v = await this._getBackendOS();
const _ = JSON.stringify(r);
let S, k, C;
v === 1 ? (k = _, S = f, C = "windows_temp_file")
        : (k = _, S = f, C = "stdin");

On Windows the input channel is labelled windows_temp_file; on every other platform it
is stdin.

Observed in the product

Cursor’s own hook execution log confirms this at runtime: every hook entry in the log
is tagged windows_temp_file. The log shows the full JSON payload under “Input:”, so
Cursor clearly has the payload — the question is how the child process receives it.

Why this matters

The documented Claude Code hook contract is JSON on stdin, JSON on stdout, exit code 2
to deny
. Third-party hooks written to that contract read stdin. A well-behaved hook that
finds stdin empty exits 0 silently — which is correct hook behaviour, and precisely what
makes this failure invisible.

On this machine, two PowerShell hooks read stdin via [Console]::In.ReadToEnd() and exit
silently on empty input. They work correctly under the Claude Code extension and produce
their advisory output there. Under Cursor they may never have run to completion, with no
error, no output, and nothing in the UI to indicate it. Their entire purpose — steering the
agent toward the correct knowledge graph before it reads code — would have been silently
absent for every Cursor session.

Minimal reproduction

Add to ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "node \"C:/path/to/stdin-probe.js\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

stdin-probe.js:

const fs = require('fs');
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', c => { raw += c; });
process.stdin.on('end', () => {
  fs.appendFileSync('C:/path/to/stdin-probe.log',
    new Date().toISOString() + '  stdin bytes=' + raw.length + '\n');
  process.exit(0);
});
setTimeout(() => {
  fs.appendFileSync('C:/path/to/stdin-probe.log',
    new Date().toISOString() + '  stdin NEVER ENDED (no data, no EOF)\n');
  process.exit(0);
}, 3000);

Trigger a file read in the agent, then inspect the log.

  • stdin bytes=<n> with n > 0 → stdin is delivered; only the log label differs, and this
    issue is cosmetic.
  • stdin bytes=0 or stdin NEVER ENDED → hooks written to the documented contract receive
    nothing on Windows. Compare against the same hook under Claude Code on the same machine.

Requested fix

Deliver the payload on stdin on Windows as on every other platform. If a temp file is
required for an internal reason, keep stdin as the primary channel and treat the file as an
addition (for example an env var pointing at it), so that hooks written to the published
contract continue to work unmodified across hosts.


Open items still to be tested

Listed so nobody assumes they are settled.

# Question How to settle it Why it matters
1 Is stdin actually delivered to hooks on Windows? The stdin-probe.js reproduction above, run under Cursor and again under Claude Code on the same machine Decides whether Issue 2 is cosmetic or a silent correctness bug
2 Do PreToolUse hooks run at all under Cursor? Filter the hook execution log for preToolUse. Only postToolUse entries were observed in the samples captured If PreToolUse never fires, gating and advisory hooks are inert, which is worse than Issue 2
3 Which local change stopped the focus theft? Remove -WindowStyle Hidden from the hook commands, restart Cursor, repeat a dense burst of tool calls Measurement says the flag cannot help; if the symptom returns, that measurement does not generalise to Cursor’s spawn context and should be re-examined
4 Is focus stolen by a window appearing, or by a direct foreground call? A GetForegroundWindow() poller running while the symptom occurs, logging the owning process on every change Window creation and focus theft are not the same thing. All measurement so far watched windows, not focus. If nothing visible appears but focus still moves, the cause is a SetForegroundWindow-class call and no spawn flag will fix it
5 Are non-GSD plugin hooks a remaining Git Bash entry point? Two installed plugins declare bash "${CLAUDE_PLUGIN_ROOT}/hooks/*.sh" hooks. One such entry appeared in the hook log with a warning marker These were never rewritten to Node and remain on the hot path. If the symptom returns, this is the first suspect
6 Does the hook-log warning marker indicate a failure? Expand that entry in the hook execution log and read the error An erroring hook on every tool call is its own problem
7 Does a SessionStart / Stop hook behave the same? Force a new chat or a stop while a window watcher runs. Only PreToolUse and PostToolUse paths have been exercised so far Session-lifecycle hooks run heavier scripts and were not covered by any test above

Local mitigations applied (for reference, not a recommendation)

  1. Four Git Bash .sh hooks rewritten as Node .js equivalents, keeping the same base
    filenames so the upstream installer still recognises them as configured. This is the
    change most likely to have resolved the symptom.
  2. -WindowStyle Hidden left on the PowerShell hook commands. Measurement says it does
    nothing; it is retained only because the system is currently working and removing it
    without cause would be a worse bet than leaving it. It is not offered as a fix.
  3. The same flag was removed from nested PowerShell calls inside a local script, where it
    was measurably harmful (it hid the calling terminal permanently).