Windows: plugin hooks spawn visible cmd.exe/conhost.exe windows despite windowsHide:true

Resolved 💬 6 comments Opened Feb 20, 2026 by ls-edwin-bekaert Closed Jun 13, 2026

Description

On Windows 11 Enterprise, plugin hooks spawn visible cmd.exe / conhost.exe console windows that flash on screen. This happens despite Claude Code correctly setting windowsHide: true on the spawn() call.

Each hook invocation creates a brief but visible console window flash. With multiple hooks (PreToolUse, PostToolUse, SessionStart, etc.), this produces frequent visual disruption during normal usage.

Environment

  • Claude Code: 2.1.45
  • OS: Windows 11 Enterprise 10.0.26200
  • Node.js: v22.20.0
  • Terminal: Windows Terminal
  • Shell: Git Bash (MINGW64)

Steps to Reproduce

  1. Install Claude Code on Windows
  2. Install any plugin that registers hooks (e.g., hooks with "type": "command", "command": "node script.js")
  3. Start a session and use any tool that triggers a hook
  4. Observe brief cmd.exe console windows flashing on screen

Evidence from Windows Security Audit Logs

Enabled process creation auditing (Event ID 4688). The process chain for each hook invocation:

node.exe (claude) -> cmd.exe -> conhost.exe (VISIBLE WINDOW)
                             -> node.exe (hook script)

Key audit log entries (timestamps within same millisecond):

| Time | Parent | New Process | Note |
|------|--------|-------------|------|
| 10:20:48.612 | node.exe | cmd.exe | CC spawns hook via shell:true |
| 10:20:48.627 | cmd.exe | conhost.exe | Console Host = visible window |
| 10:20:49.033 | node.exe | cmd.exe | Next hook fires |
| 10:20:49.040 | cmd.exe | conhost.exe | Another visible window |

Root Cause Analysis

The hook execution code (extracted from binary at offset ~122596477) does:

// Simplified from minified ow$() function
let command = hookConfig.command; // e.g. "node script.js"
let child = child_process.spawn(command, [], {
  env: env,
  cwd: projectDir,
  shell: true,       // uses cmd.exe on Windows
  windowsHide: true  // sets CREATE_NO_WINDOW flag
});

windowsHide: true is correctly set (confirmed all 12 occurrences in binary are true). However, conhost.exe is still being spawned by cmd.exe on this Windows configuration.

Standalone test shows windowsHide works

We created a standalone test that spawns processes with identical options:

const { spawn } = require('child_process');
spawn('node -e "process.exit(0)"', [], {
  shell: true,
  windowsHide: true,
  stdio: 'pipe'
});

This test does NOT produce visible windows when run from a separate terminal. Yet the same shell: true + windowsHide: true combination from within Claude Code DOES produce visible windows. This suggests something about Claude Code's process context (e.g., console attachment state, Windows Terminal ConPTY interaction, or SEA binary characteristics) causes conhost.exe allocation despite CREATE_NO_WINDOW.

Attempted Workarounds

| Approach | Result |
|----------|--------|
| windowsHide: true in hook scripts' own execSync/spawn calls | Fixes secondary spawns (git commands within hooks), not the primary hook spawn |
| ComSpec=bash.exe to bypass cmd.exe | Breaks variable expansion ($VAR expanded by bash vs passed literally by cmd.exe) |
| CLAUDE_CODE_SHELL_PREFIX | Still goes through shell: true / cmd.exe, cannot bypass the issue |
| StatusLine launcher (pure Node.js, no bash wrapper) | Fixes statusLine-specific flashes but not hook flashes |

Suggested Fix

For hooks on Windows, consider one of:

  1. Use shell: false for hook commands that don't require shell interpretation. Most hooks are simple node script.js invocations. Parse the command and spawn directly:

``js
// Instead of: spawn(command, [], { shell: true, windowsHide: true })
// Parse and use: spawn('node', ['script.js'], { shell: false, windowsHide: true })
``

  1. Use cmd.exe /d /s /c directly with CREATE_NO_WINDOW via spawn options, bypassing Node.js's shell: true handling which may not correctly propagate the flag in all contexts.
  1. Investigate why windowsHide: true is ineffective in the Claude Code process context (SEA binary? ConPTY interaction? Console attachment state?) when it works in standalone Node.js.

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗