You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Windows: Console window flashing when executing tools #14828
I have searched existing issues and this hasn't been reported yet
This is a single bug report (please file separate reports for different bugs)
I am using the latest version of Claude Code
What's Wrong?
Description
When using Claude Code on Windows (Windows Terminal), a console window briefly flashes/appears every time a tool is executed (Bash, Grep, Glob, Read, etc.).
Environment
OS: Windows
Terminal: Windows Terminal
Platform: win32
Steps to Reproduce
Run Claude Code on Windows
Execute any command or let Claude use any tool
Observe brief window flash
Expected Behavior
Tools should execute without visible window flashing.
Possible Solution
When creating child processes on Windows, use CREATE_NO_WINDOW flag or STARTUPINFO.dwFlags = STARTF_USESHOWWINDOW with wShowWindow = SW_HIDE.
I'm also experiencing this issue, and it extends beyond just tool execution.
Additional Context
The console window flashing also occurs when plugin hooks execute. For example, the claude-mem plugin registers hooks for SessionStart, UserPromptSubmit, PostToolUse, and Stop events that run node and bun commands. Each execution creates a visible console window.
What I tried (unsuccessfully)
VBS wrapper with hidden window - Wrapped commands in a VBS script using WScript.Shell.Run with vbHide flag. Failed due to quote escaping issues with cmd /c.
PowerShell -WindowStyle Hidden - Wrapped commands with powershell -WindowStyle Hidden -Command "...". The PowerShell window was hidden, but the spawned node.exe/bun.exe processes still created their own console windows.
Root Cause
On Windows, console applications (like node.exe, bun.exe) create console windows by default when spawned. The only reliable way to prevent this is to use the CREATE_NO_WINDOW flag when calling CreateProcess().
Environment
OS: Windows 11
Terminal: Windows Terminal
Claude Code with claude-mem plugin
Request
Please consider implementing CREATE_NO_WINDOW for all child process spawning on Windows, including:
Tool execution (Bash, Grep, etc.)
Plugin hook execution
MCP server processes
This would significantly improve the Windows user experience.
Reacted by dox187, recursion, Dawood Saleh, Jay Madore and Jacob Rastad
I've recently started to experience this on windows and it's a really bad user experience. You can't do anything else while claude is running or it'll get in the way
Reacted by recursion, Dawood Saleh, Albino-crreator, Jay Madore, gbechtel-beck and roslovaxenya-ai
Hook execution also affected - critical UX impact for plugin users
User Experience Impact:
I'm experiencing this same issue with plugin hooks, not just tool execution. Every time a claude-mem plugin hook executes (SessionStart, UserPromptSubmit, PostToolUse, Stop), a Node.js console window flashes on screen, often stealing keyboard focus mid-sentence. This happens dozens of times per session and significantly disrupts workflow.
Root Cause:
The issue is in Claude Code's hook execution function (appears to be GE1() in the bundled CLI). The spawn call is missing the windowsHide option:
Standard Node.js practice for Windows subprocess spawning
Why Workarounds Don't Work:
All attempted workarounds fail because:
PowerShell -WindowStyle Hidden → cmd.exe spawns first before PowerShell
VBScript wrappers → Can't handle stdin properly (hooks need JSON input via stdin)
Batch files with start /b → Still creates initial cmd.exe window
PATH shims → Would also flash windows
The ONLY solution is fixing the spawn call at the source.
Impact Scope:
This affects:
✅ All Windows users of Claude Code
✅ Every tool execution (Bash, Read, Write, etc.)
✅ Every plugin hook execution (SessionStart, UserPromptSubmit, PostToolUse, Stop)
✅ MCP server startups via hooks
Business Impact:
For professional Windows users, this makes Claude Code nearly unusable. The constant window flashing:
Disrupts typing mid-sentence
Breaks focus during code reviews
Makes screen recording unprofessional
Prevents use in client-facing environments
Request:
Could this be prioritized for the next patch release? The fix is a one-line change that would immediately improve UX for all Windows users. I'm happy to test a beta build if that would help.
Environment:
OS: Windows 11 64-bit
Claude Code version: v2.1.2
Plugin: claude-mem v9.0.0
Terminal: PowerShell 7.x
Thank you for considering this!
Reacted by Mike Glenn, Jay Madore, gbechtel-beck, Ravinder Singh, jessieJiangs, Christian Rondeau and Steve A
Hi team — adding an update since my duplicate report (#16880) was closed.
I can still reproduce the same “console window flash” on Windows when tools run, now on the VS Code extension as well:
Claude Code for VS Code extension: 2.1.11
OS: Windows 11
Where it happens: VS Code (integrated terminal / Claude panel) — every Bash/tool invocation triggers a brief node.exe/console window flash
The worst part is that the flashing window steals foreground focus while it appears. During that moment I can’t interact with VS Code (keyboard input gets interrupted / clicks can be lost), so it effectively blocks other work. This is not just distracting — it actively breaks workflow.
Could you share the current status and when you expect a fix (e.g., a target version or timeframe)?
Thanks.
Reacted by Mike Glenn, Jay Madore and Ravinder Singh
This is extraordinarily disruptive when using CC on Win11. One workaround is to use an admin terminal but doing so creates its own downstream issues and shouldn't be necessary. This is seriously impacting workflow - desperate for a fix.
Update: Another workaround for those finding this really annoying; commands run by agents don't appear to trigger the flashing cmd window issue (at least for me on win11, Terminal, CC CLI, non-admin account), so if you can delegate a task via agents I recommend doing so.
Reacted by Mike Glenn, Jay Madore, gbechtel-beck and Ravinder Singh
FYI I was plagued by this issue (it was immensely annoying) and finally today I got it fixed, this did the trick (note I have my git installed via scoop):
Same issue here. Using Claude Desktop (not CLI) on Windows 11 with working directory on WSL (\wsl.localhost\Ubuntu\home...).
Every message triggers a visible /usr/bin/bash --login -i -c cygpath -u \wsl.localhost... window for git repo detection. The window flashes briefly and steals focus, making it very disruptive.
Attempted workarounds (none worked):
Setting SHELL to cmd.exe in ~/.claude/settings.json
Setting SHELL to powershell.exe
Removing SHELL env entirely
The windowsHide: true fix on child_process.spawn() would resolve this.
Environment:
Windows 11
Claude Desktop (latest)
Git for Windows installed (C:\Program Files\Git\cmd\git.exe)
Working directory: WSL Ubuntu via \wsl.localhost\
On closer measurement, I was wrong to attribute the flashing to the statusLine and hook path on this version. Re-testing on v2.1.212 native (Windows 11):
The bundled runtime's statusLine/hook spawner does pass windowsHide: true on this build - all three spawn forms, including the statusLine path (spawn(cmd, [], {..., windowsHide: true})).
Empirically: an EnumWindows watcher polling every 30-40 ms over 1191 statusLine bash.exe spawns in ~50 s (13 concurrent sessions) recorded zero new visible top-level windows of any class (ConsoleWindowClass / PseudoConsoleWindow). conhost.exe processes are created but no window ever becomes visible.
So on 2.1.212 the statusLine/hooks are not the source of the visible flashing, contrary to my earlier comment.
The console spawns I can still see being created are stdio MCP servers, launched as:
(grandparent claude.exe, allocating a fresh console 0x4). If anyone is still getting flashes on a current build, worth checking whether the windowsHide flag is being passed on the MCP stdio server spawn path specifically, as opposed to the statusLine/tool paths - they appear to be separate spawn sites. Repro is trivial: configure any stdio MCP server whose command is npx (so it resolves through cmd.exe) and watch for a console flash each time the server boots/reconnects.
Happy to test a build with windowsHide on the MCP spawn path.
All the diagnostics I've done point to the statusline. I installed ccstatusline, got flashing windows; replaced ccstatusline with a custom powershell script, still get flashing windows. Disabled all my stdio MCPs (had Aspire and cwm-roslyn-navigator), still got flashing windows. Disabled my custom statusline altogether, and no more flashing windows.
Adding a data point that isolates this to Claude Code CLI's spawn of hook commands, not the plugin/tool being invoked.
What we ruled out downstream
I opened alexgreensh/token-optimizer#104 about the same flashing during normal work on Windows and tried three progressively more aggressive workarounds inside the plugin:
VBS wscript //B wrapper around every hook command — no visible reduction
pythonw.exe preference in the plugin's own launcher (later landed upstream by the plugin author as _maybe_swap_to_pythonw) — no visible reduction either
Skipping the intermediate bash.exe entirely by rewriting hooks.json to invoke pythonw run.py directly as the first token — no reduction (user reported it looked slightly worse)
After the plugin author landed v5.11.72+ with the console-safe python swap and fixed a separate console-allocating Scheduled Task in the plugin itself, the observation on my Windows box was:
✅ Idle "5-minute pops" from the plugin's scheduled task: gone
❌ Per-tool-call flashes during active work: unchanged
Since no downstream fix removed the per-tool-call flash, the console allocation must be happening in the cmd.exe /c "<hook command>" wrapper that Claude Code CLI uses to spawn the hook — before any bash, python, or wscript inside it can matter. This is consistent with @mattgphoto's process sampling in #104 (111 bash.exe vs only 2 cmd.exe in 28s of agentic use, but the transient cmd.exe /c still allocates a console).
Concrete ask
If Node's child_process.spawn(cmd, { shell: true, ... }) for hook execution passed windowsHide: true, the cmd.exe /c shim would still exist but its transient console wouldn't be shown. That's a one-line change on the CLI side and would eliminate the flash regardless of what the hook command does inside (bash, python, wscript, direct .exe).
The plugin author documented this as an external limit in-source in v5.11.72+ (comment in #104).
Environment
Windows 11 Pro 26200
Claude Code (VSCode extension)
Node child spawn on Windows via shell: true
Python 3.13.14 (python.org), git-bash available
Reproducible with any plugin that registers hooks; more visible with plugins that fire many (Token Optimizer registers ~26 hooks across lifecycle events, so this is the loudest surface but not the only one)
Reproduced this independently and want to share two findings that add some precision beyond "a console briefly appears" — one about why it can look like a full terminal window rather than a faint flash, and one about what's actually triggering it in a hook-heavy setup.
It's not always just a conhost.exe flash — it can be a full WindowsTerminal.exe window
If HKCU:\Console\%%Startup has DelegationConsole/DelegationTerminal set to Windows Terminal's GUID ({2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69} / {E12CFF52-A866-4C77-9A90-F570A7AA2C6B}) — the default on many Windows 11 setups — then any unhidden console-allocating child process gets delegated to a brand-new WindowsTerminal.exe -Embedding process, not just a bare conhost. That's a much more visually disruptive and confusing symptom than a "flash": it reads as an entire terminal application popping up and disappearing, with its own taskbar entry, which is probably why some reports in this thread describe "a program opening" rather than a flicker.
I confirmed this is really happening (not just theorized) by polling window visibility directly via the Win32 API rather than only watching for process creation:
Add-Type-TypeDefinition @"using System;using System.Runtime.InteropServices;public class Win32 { [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);}"@# poll Get-Process | Where ProcessName -match 'cmd|powershell|conhost|WindowsTerminal|OpenConsole'# and check [Win32]::IsWindowVisible($proc.MainWindowHandle) every ~40ms
This caught multiple genuinely-new WindowsTerminal.exe -Embedding processes (fresh PIDs each time, IsWindowVisible = true, non-null MainWindowTitle) appearing and disappearing within the same second-or-two window — i.e. directly-observed, not inferred from CPU/process churn.
What's triggering it: PostToolUse command hooks, compounded across concurrent sessions
Cross-referencing a WMI __InstanceCreationEvent process trace against the exact timestamps the visible windows appeared, the dominant activity at each flash was a PostToolUse hook (a command-type hook registered in settings.json, e.g. one that runs after every Bash tool call) spawning bash.exe — sometimes as a nested bash.exe -c "bash.exe ...script.sh" chain. Each spawn gets its own conhost.exe.
Two things made this much worse in my repro, which might explain why severity varies so much between reports:
It scales with hook count. A single Bash-matched PostToolUse hook fires once per Bash call; a config with several matchers (Bash|Edit|Write|MultiEdit|Agent|Task etc.) on PreToolUseandPostToolUse can spawn 4–7+ child processes per single tool call.
It's per-session, and sessions don't share the budget. I had two Claude Code sessions active (one Desktop-app session, one CLI-resumed session) with the same global hooks configured — both were spawning independently, so the effective flash rate roughly doubled with a second concurrent session. Anyone running multiple Claude Code windows/sessions at once (common with the Desktop app) will see this compound.
I could not pin the exact single spawn call missing CREATE_NO_WINDOW/windowsHide — dozens of hook spawns fired per minute while only a handful produced a visible window, so whatever's happening is inconsistent rather than universally broken (i.e. most hook spawns are properly hidden; something about a subset isn't). But the mechanism — hook-command spawn → occasionally-visible window, worse with more hooks and more concurrent sessions, and rendered as a full WindowsTerminal.exe window rather than a faint flash when Terminal delegation is enabled — seems like a useful, reproducible angle for narrowing down the actual missing flag.
Happy to share the full WMI trace / polling script if useful for a maintainer trying to reproduce this deterministically.
Following up on my own comment above, and on @lnorton89's registry finding, with
a measurement of what that delegation setting actually costs.
Short version:DelegationConsole/DelegationTerminal does not just change how
the flash looks. It decides whether a lingering console host costs ~2 MB or
grows unbounded. I have ~6.5 days of process-memory sampling across both
configurations on the same machine.
First, a correction to my own table above: I listed "Windows Terminal default
profile — not involved". That entry was about which profile Windows Terminal
opens, and it stands. The console delegation setting is a different mechanism
and it turns out to matter a great deal.
1. Claude Code does leave lingering console hosts (reproducing @cyneta's "25 hidden conhost")
Measured just now:
conhost.exe children of claude.exe : 8
ages : 20.8, 46.3, 46.3, 46.7, 81.9, 93.0, 93.0, 93.0 h
private bytes, oldest (93.0 h) : 1.9 MB
private bytes, youngest (20.8 h) : 2.5 MB
all 28 conhost.exe on the machine : 43.7 MB total
They accumulate and survive for days. On Console Host delegation this is
harmless — the 93-hour-old one holds the same 1.9 MB as the 46-hour-old ones,
so the host itself does not grow. That is the control for what follows.
2. On Windows Terminal delegation, the host grows ~320 MB/h for as long as the console lives
Same machine, earlier, when a console-allocating child was delegated to WindowsTerminal.exe. Sampled every ~5 min by a memory watchdog writing CSV;
recomputed from that raw CSV, two independent WindowsTerminal.exe instances:
Two separate processes, near-identical rate. Growth is time-driven, not
output-driven: split into four 6-hour bands the rate is 316 / 365 / 320 / 321
MB/h regardless of whether anything was being printed. A buffer filling with
console output would be near zero on an idle night.
Effect on system memory pressure, same CSV:
during the leak window (n=458): median commit 77.9 %, 4 % of samples above 95 %
after the console was gone (n=1113): median commit 80.0 %, 0 % of samples above 95 %
Honest scoping — the trigger was mine, not Claude Code's. The process that
allocated that console was my own long-running service, launched with CREATE_NO_WINDOW | DETACHED_PROCESS. DETACHED_PROCESS wins, the child gets no
console, and when it later starts a console program Windows must create a fresh
one. Dropping DETACHED_PROCESS made the WindowsTerminal.exe process disappear,
which is what pins the causality. This is the same "rule out your own software"
exercise as in my earlier comment, one layer deeper.
So I am not claiming Claude Code leaked 13 GB — its hook consoles are
transient. What I am claiming is that the two halves meet: Claude Code supplies
console hosts that linger for days (§1), and the delegation setting decides
whether such a host is a 2 MB conhost or a Windows Terminal process on a
~320 MB/h ramp (§2). On a default Windows 11 install pointing at Windows
Terminal, that combination is reachable without anyone doing anything unusual.
3. A mitigation that costs nothing while the flag question is settled
Setting the default terminal application to Windows Console Host caps the damage
when a spawn does get a visible console: a conhost flash instead of a full WindowsTerminal.exe -Embedding window with its own taskbar entry, and the
measured growth does not occur. Current state here, with Windows Terminal
1.24.11911.0 still installed:
(Settings → System → For developers → Terminal, or Windows Terminal's own
"Default terminal application" dropdown.)
It is a mitigation, not a fix. It makes the missing windowsHide cheap instead of
expensive, and does nothing about the flashing itself. The request in my earlier
comment stands.
Happy to share the watchdog CSV or the sampling script if that helps anyone
reproduce this deterministically.
Environment: Windows 11 Home 10.0.26200, Claude Code 2.1.207 (VS Code extension),
3 hooks configured (one PostToolUse on Edit|Write, two UserPromptSubmit), no
custom statusLine. Sampling window 2026-08-03 → 2026-08-10, 1919 samples.
@sorenssl I can't really tell if you're positing that WindowsTerminal.exe memory usage grows unbounded as a general rule, but it's worth pointing out that on my system, it doesn't. I have one process running on my machine that has been running around 9 days and has ~174MB WS and ~616MB PM. It's currently running 5 tabs, 3 of which have claude.exe running in them.
Confirming the Terminal-delegation mechanism from a different angle, plus a
narrower hypothesis for why only some hook spawns leak a window.
Short recap of what I posted in July, since it is the premise here: a
console-subsystem process gets its window created VISIBLE before it can hide
itself, so powershell -WindowStyle Hidden provably still flashes on Win11 with
Terminal delegation, while a GUI-subsystem (winexe) binary spawned identically
does not.
On why it is inconsistent: nesting, not hook count
@lnorton89 notes that most hook spawns are hidden and only a subset leaks. My
data points the same way, and suggests the split is nesting.
On this machine I have two groups running side by side. Five hook entries whose
command is a single direct node "<script>" <args> have never produced a window,
across months. A plugin whose seven hook commands ran under bash with export PATH="$($SHELL -lc 'echo $PATH' 2>/dev/null):$PATH" at the head produced
them constantly, including on PostToolUse with matcher: "*", so once per tool
call. On Windows $SHELL is /bin/bash.exe, so that prelude is a full login
shell spawned to read one variable.
That matches the bash.exe -c "bash.exe ...script.sh" chains in your WMI trace.
The plausible reading is that the runtime hides the process it starts, and the
leak is the grandchild the hook's own command line spawns, which nothing passes CREATE_NO_WINDOW to.
If that holds, the fix is narrower than "hide all hook spawns": a hook command
that spawns its own shell is outside the runtime's control, so either the flag
has to survive into the grandchild, or the hook has to be started in a way that
cannot allocate a console at all.
Wrapping hook commands so they start from a GUI-subsystem process removes the
symptom regardless of which spawn is at fault, since there is no console to
create. That has held for me across four consecutive releases of that plugin,
with the hooks still executing normally.
What I cannot answer
Same as @lnorton89: I never pinned the specific spawn site missing CREATE_NO_WINDOW / windowsHide. The launcher approach sidesteps the question
rather than answering it, so this is a mitigation, not a diagnosis. A runtime fix
is still worth having.
Unrelated latency note, since hook count came up: running several handlers in one
process instead of one process per handler is worth doing on Windows for its own
sake. Node startup is around 75 ms here, so three separate handler spawns on
every Write were adding roughly 225 ms of pre-tool latency. That is a
performance fix, not a window fix.
Correction (later comment): the final claim below — that the leak is tied to hosting a process with no console of its own — is wrong. A missing control (phase C) showed ordinary processes behave identically. Corrected measurement here: #14828 (comment)
@godefroi Good push — and no, unbounded growth as a general rule is not what I
meant, and your 9-day instance shows it would be wrong. I ran a controlled test
overnight to find where the line actually sits. It turns out your case and mine
differ by one thing, and it is not the application.
Two phases, 75 minutes each, same machine, same night, 76 samples per phase at
60 s intervals.
PHASE A interactive wt.exe, one idle console 76.9 -> 75.5 MB = -1 MB/h
PHASE B WT as the DELEGATED console host for a 89.2 -> 485.8 MB = 317 MB/h
detached chain with no console of its own 96 % monotonic
Phase A behaves the way you describe: no measurable growth over 75 minutes
(the -1 MB/h is noise, not a decline), with handle count trending down,
929 -> 892.
Phase B pointed DelegationConsole/DelegationTerminal at Windows Terminal and
recreated the chain behind my original numbers: a process started with CREATE_NO_WINDOW | DETACHED_PROCESS (so DETACHED wins and it gets no console),
which then starts a console program — forcing Windows to create a fresh console,
which delegation hands to Windows Terminal.
The two obvious objections are both ruled out by the data:
Not startup allocation. First third of phase B: 297 MB/h. Last third:
292 MB/h. Straight line for 75 minutes, no flattening.
Not console output. The payload is time.sleep(). It writes nothing at
all. This matches the original incident, where the rate was essentially
constant across all four 6-hour bands of the day — 316 / 365 / 320 / 321 MB/h
— which is not the shape an output-driven buffer produces.
So the precise claim, narrower than the one I implied: Windows Terminal does
not leak as an application — it leaks in the role of console host for a process
that has none of its own. Same binary, two roles, and the entire difference
between them is the leak. You are looking at the first role. I was looking at
the second.
That distinction is what makes it relevant here rather than just my own bug.
Claude Code leaves console hosts parented to claude.exe alive for days — 8 on
this machine right now, aged 44 h to 116 h. Under Console Host they cost
1.9–2.5 MB each and do not grow with age: the 116-hour-old one holds 1.9 MB,
exactly what the 70-hour-old ones hold. Under Terminal delegation, that same
lingering host is on the ramp above. The registry value decides which of the
two you get.
One piece of credit I owe from my previous comment: @romemozu listed "switching
the default terminal from Windows Terminal to Console Host" among their
workarounds back on 6 July, a month before I presented the same switch as if it
were new. What I can add is why it is worth more than a cosmetic fix — the
numbers above are the difference between the two settings. Their caveat still
stands too: the internal git / gh / tasklist spawns from claude.exe
cannot be hidden from the user side at all. That fits what I see here — three
hooks configured, eight lingering hosts.
Scope, since one machine is one machine:
Windows 11 Home 26200, Windows Terminal 1.24.11911.0, single box.
I did not capture the delegated process's command line, so -Embedding is the
reasonable reading of what it was, not something I proved.
Phase A's process was launched by me with a profile and a tab; phase B's was
created by Windows. That is the point of the comparison, but it is more than a
one-variable difference.
And credit where it is due: your counter-example is what produced this. I had a
rate and no boundary, which is a claim rather than a finding — it took an
instance that demonstrably does not leak to turn "does Windows Terminal leak?"
into a question with an answer. One machine disagreeing with numbers was worth
more than agreement would have been.
The test is two files — a PowerShell harness that flips the setting, samples,
and restores it, plus the three-level process chain. I will post both if anyone
wants to run the same two phases elsewhere; a second machine would settle the
scope question faster than more measurements from mine.
I ended it with a claim my own test did not isolate: that Windows Terminal leaks
"in the role of console host for a process that has none of its own". The two
phases changed two variables at once — who created the Terminal process, and
whether the hosted process had a console of its own — and I attributed the whole
effect to the second.
Here is the missing control. Phase C: Terminal as the delegated console host
again, but hosting an entirely ordinary process, launched with CREATE_NEW_CONSOLE from a parent that has its own console. No DETACHED_PROCESS, no console-less parent anywhere in the chain.
A interactive wt.exe, started by me -1 MB/h (75 min, n=76)
B delegated host for a DETACHED chain with no console 317 MB/h (75 min, n=76)
C delegated host for an ordinary CREATE_NEW_CONSOLE process 319 MB/h (39 min, n=40)
C matches B. The DETACHED_PROCESS chain has nothing to do with it — it was
just the path I happened to find this through. The qualifier in my sentence is
wrong, and the corrected claim is both simpler and broader:
Windows Terminal grows ~320 MB/h whenever Windows makes it the delegated
console host, regardless of how the hosted process was started. It does not grow
as a terminal you opened yourself.
That makes the delegation setting the entire variable, which if anything
sharpens the practical point in this thread: any console-allocating spawn that
is not hidden lands on that path when delegation points at Terminal — including
the hook and MCP spawns being discussed here.
Two notes on method, since I got one of them wrong:
My first attempt at phase C measured nothing. Start-Process did not create a
new console, so no host was ever delegated. Because I logged conhost,
OpenConsole and WindowsTerminal separately with their parents, this showed up
as "no new console at all" rather than a result. Without that instrumentation
I would have reported "Windows does not delegate ordinary processes", which is
false.
On whether phase B's growth could be accumulation of adopted consoles rather
than a leak: the chain ran once, one Terminal PID throughout, and the
per-minute deltas show no steps — 74 intervals, mean 5.26 MB, none above 3x
the mean. A scheduled console process on this machine did fire mid-phase; that
minute grew 6.1 MB against neighbours of 5.5, 5.5, 7.5 and 4.9. Thread count
fell from 92 to 86 across the phase. Adoption would show steps and rising
threads; neither is present.
Same machine and Windows Terminal version as before (Windows 11 Home 26200,
WT 1.24.11911.0), so the scope caveat stands: one box.
Real talk here, drop your LLM for a second and turn on your brain. microsoft/terminaldoes have some reported "memory leak" issues, but every real reported issue of that type is tied to output volume, and the one you believe you've discovered here is over 2x the known worst-case output-driven scenario. If a bug like that existed, it'd be one of the most trivially reproducible, universally-triggered bugs in a massively-used, actively-staffed Microsoft repo (any detached process that allocates a console under default Windows 11 settings). It isn't filed anywhere.
Two independent data sets in this thread now point at the same spawn-path property, so here is a short synthesis for whoever picks this up.
Mechanism (my May 20 comment): the hook dispatch pipeline wraps every hook command in bash -c "..." even when argv[0] is a plain .exe. bash.exe is a console-subsystem binary, so each dispatch materializes a conhost/Terminal window regardless of the child's own subsystem or any -WindowStyle Hidden on the inner command.
Independent confirmation (@NiceLeader, Jul + Aug 10): on one machine, five hooks invoked as a single direct node "<script>" <args> never produced a window across months, while seven bash-wrapped hook commands flashed constantly. Same split, measured from the other side.
The actionable slice for maintainers is still small: when a hook command's argv resolves to a single executable with no shell metacharacters, spawn it directly with windowsHide: true instead of going through the bash wrapper; keep windowsHide on the wrapper spawn for genuine shell commands. That removes the dominant flash source for native-binary hooks without touching the general Bash-tool path.
I ran a controlled A/B on the wrapper question, since two readings of it were
circulating in this thread.
Setup: the same executable declared twice as a PostToolUse hook in one
settings file, differing only in declaration form. Isolated headless session
(claude -p --settings <file>), process creation watched via Win32_Process
with parent resolution. Payloads write a marker file and then sleep, so nothing
is lost to sampling.
// A - string form
{ "type": "command", "command": "C:/.../launcher.exe -CommandBase64 <payload>" }
// B - command + args
{ "type": "command", "command": "C:/.../launcher.exe",
"args": ["-CommandBase64", "<payload>"] }
Both fired. Result:
form
processes created
parent of the exe
A, string
bash.exeandlauncher.exe
wrapper present
B, args
launcher.exe only
claude.exe, directly
@marcodelpin's premise holds. A hook command written as a string is wrapped
in bash -c even when argv[0] is a plain .exe with no shell metacharacters.
I had read a difference between two sets of hooks on my own machine as evidence
that the runtime inspects argv[0]. It does not. The difference was the args
field, and I was wrong about the cause.
What is new: args is an existing bypass. Declaring command plus an args array makes the runtime spawn the executable directly, with no shell in
the chain. That works today, with no runtime change, and it is why hooks
rewritten into that form have no bash parent. Any plugin whose hook command is
a fixed executable can convert to it now.
It also means the direct-spawn machinery already exists. The distance between
that and @marcodelpin's proposal is only the derivation step: deriving args
automatically when a string command has no shell metacharacters would route
those commands through a path that already works.
Scope note so this is not read as more than it is. This measures spawn shape,
not which spawn allocates the visible window. On this machine the bash wrapper
itself has never produced one, so my flashes came from further down the chain.
That may differ from the environment in @marcodelpin's measurements, and the
two questions are worth keeping separate.
A scoping correction to my Aug 10/12 comments, after trying to reproduce the
flash deliberately on this machine and failing in every configuration.
I ran the grandchild matrix from a hidden parent console: bash -> node
(2.5 s sleep), bash -> node -> spawnSync(bun, {windowsHide:true}), bash -> node -> spawnSync(bun) with no flag at all, and spawn(bun, {detached:true, stdio:'ignore'}) mirroring a daemon respawn.
Window visibility polled via IsWindowVisible, not process creation. Zero
visible windows in every cell, including the deliberately-unhidden ones.
Then I checked the one machine property @lnorton89 flagged: HKCU:\Console\%%Startup here is {B23D10C0-E52E-411E-9D5B-C09FDF709C7D} -
classic conhost delegation, not Windows Terminal. Under conhost delegation a
child of a hidden console inherits that console, so an unhidden grandchild has
nothing to pop.
Two consequences:
My earlier "the bash wrapper is hidden and never flashes here" observation
is delegation-scoped evidence, not a general result. On WT-delegation
machines ({2EACA947-...} / {E12CFF52-...}) the same spawn shapes may
behave differently, and my machine cannot arbitrate which layer of the
chain allocates the visible window there.
Anyone posting "cannot reproduce" (me included, apparently) should attach
that registry value; a conhost-delegation machine may be structurally
incapable of the dramatic per-call WindowsTerminal.exe symptom. One-liner: Get-ItemProperty "HKCU:\Console\%%Startup" | Select Delegation*
For the record, one theory I tested and refuted rather than posted: a detached:true, stdio:'ignore' daemon respawn from a hidden parent does NOT
create a window here - libuv passes DETACHED_PROCESS on Windows, so the child
gets no console at all, despite the Node docs' "own console window" phrasing.
Confirming this reproduces on a vanilla install before any hooks or plugins are involved: with just Claude Code + Git Bash out of the box, every Bash tool call flashes its own console. The details below add a data point on how a hooks-heavy setup multiplies the frequency on top of that baseline.
Environment
Windows 11 Enterprise LTSC 2024 (10.0.26100)
Claude Code v2.1.226, native terminal (no WSL/Dev Container)
Git Bash shell backend
Plus oh-my-claudecode installed: PreToolUse / PostToolUse / PostToolUseFailure / SessionStart / Stop hooks and a statusline command, all spawning node.exe
Observed
Baseline (vanilla Claude Code, no hooks/plugins):
Every shell tool call spawns bash.exe with a visible console flash. Command output and exit status are unaffected — purely visual.
With the hooks-heavy setup layered on top:
Each hook event additionally spawns node.exe through a cmd.exe wrapper, which allocates its own console. PreToolUse/PostToolUse match all tools, so a single tool call stacks 2–4 distinct flashes on top of the baseline (bash.exe + hook spawns); a typical session (~40 tool calls) racks up 80+ flashes.
The statusline command is the most frequent offender since it renders on every UI update — near-constant flashing while typing.
Other spawn paths flash on their own schedules: shell snapshot at session start, IDE-detection polling, MCP stdio servers.
Tried
Routing work through the PowerShell tool path doesn't help — the Git Bash snapshot/backend still flashes, and hook events fire for it too since they match all tools.
No settings.json option appears to control window suppression for spawned children.
Given what #27115 found (flashing persists even where windowsHide: true is set on the spawn call, pointing at the cmd.exe wrapper layer), it looks like each spawn path needs CREATE_NO_WINDOW applied consistently — or a single global windowsHide-style setting covering all child spawns (shell tools, hooks, statusline, snapshots, MCP stdio) would resolve this whole family at once.
Happy to test any experimental build or flag on this machine.
Still reproducing on Windows 11 in the Claude desktop app (build 1.46388.2). The focus theft is the painful part: the window takes keyboard focus while I am typing, so characters land in a console that closes a second later.
Two additions:
The flash follows hook spawns as well as tool calls, and the count scales with the number of hooks. Five separate PreToolUse entries in my repo flashed roughly fifty windows across a ten-call turn, which is why we consolidated them into one process. Three SessionStart hooks land the remaining burst while I type my first prompt.
The declaration form does not matter: structured command plus args flashes the same as a single command string.
The workaround that fixed session start for me: point the hook at pythonw.exe, a GUI-subsystem binary Windows allocates no console for, and have it spawn the real command with CREATE_NO_WINDOW. Two catches. Pass the std descriptors explicitly, or close_fds=True leaves the child with invalid handles and its output is discarded silently. And propagate the child's exit code, or a PreToolUse guard's exit 2 stops blocking.
Passing windowsHide: true when spawning hooks would remove the need for any of this.
Correction: an earlier version of this comment said structured-form hooks appeared not to flash. That was wrong.
Activity
GigiTiti-Kai commentedon Dec 27, 2025
I'm also experiencing this issue, and it extends beyond just tool execution.
Additional Context
The console window flashing also occurs when plugin hooks execute. For example, the
claude-memplugin registers hooks forSessionStart,UserPromptSubmit,PostToolUse, andStopevents that runnodeandbuncommands. Each execution creates a visible console window.What I tried (unsuccessfully)
VBS wrapper with hidden window - Wrapped commands in a VBS script using
WScript.Shell.RunwithvbHideflag. Failed due to quote escaping issues withcmd /c.PowerShell -WindowStyle Hidden - Wrapped commands with
powershell -WindowStyle Hidden -Command "...". The PowerShell window was hidden, but the spawnednode.exe/bun.exeprocesses still created their own console windows.Root Cause
On Windows, console applications (like
node.exe,bun.exe) create console windows by default when spawned. The only reliable way to prevent this is to use theCREATE_NO_WINDOWflag when callingCreateProcess().Environment
Request
Please consider implementing
CREATE_NO_WINDOWfor all child process spawning on Windows, including:This would significantly improve the Windows user experience.
TheDan64 commentedon Jan 3, 2026
I've recently started to experience this on windows and it's a really bad user experience. You can't do anything else while claude is running or it'll get in the way
EngDawood commentedon Jan 11, 2026
yes , it's very annoying
PakAbhishek commentedon Jan 11, 2026
Hook execution also affected - critical UX impact for plugin users
User Experience Impact:
I'm experiencing this same issue with plugin hooks, not just tool execution. Every time a claude-mem plugin hook executes (
SessionStart,UserPromptSubmit,PostToolUse,Stop), a Node.js console window flashes on screen, often stealing keyboard focus mid-sentence. This happens dozens of times per session and significantly disrupts workflow.Root Cause:
The issue is in Claude Code's hook execution function (appears to be
GE1()in the bundled CLI). The spawn call is missing thewindowsHideoption:Current Code:
Required Fix:
Precedent:
This exact fix was already implemented in:
claude-agent-sdk-typescript(Issue chore(internal): remove deno build anthropic-sdk-typescript#103)Why Workarounds Don't Work:
All attempted workarounds fail because:
-WindowStyle Hidden→cmd.exespawns first before PowerShellstart /b→ Still creates initialcmd.exewindowThe ONLY solution is fixing the spawn call at the source.
Impact Scope:
This affects:
Business Impact:
For professional Windows users, this makes Claude Code nearly unusable. The constant window flashing:
Request:
Could this be prioritized for the next patch release? The fix is a one-line change that would immediately improve UX for all Windows users. I'm happy to test a beta build if that would help.
Environment:
Thank you for considering this!
Assist-Frontier commentedon Jan 18, 2026
Hi team — adding an update since my duplicate report (#16880) was closed.
I can still reproduce the same “console window flash” on Windows when tools run, now on the VS Code extension as well:
The worst part is that the flashing window steals foreground focus while it appears. During that moment I can’t interact with VS Code (keyboard input gets interrupted / clicks can be lost), so it effectively blocks other work. This is not just distracting — it actively breaks workflow.
Could you share the current status and when you expect a fix (e.g., a target version or timeframe)?
Thanks.
jphoenix-dev commentedon Jan 20, 2026
This is extraordinarily disruptive when using CC on Win11. One workaround is to use an admin terminal but doing so creates its own downstream issues and shouldn't be necessary. This is seriously impacting workflow - desperate for a fix.
Update: Another workaround for those finding this really annoying; commands run by agents don't appear to trigger the flashing cmd window issue (at least for me on win11, Terminal, CC CLI, non-admin account), so if you can delegate a task via agents I recommend doing so.
DamianReeves commentedon Jan 25, 2026
FYI I was plagued by this issue (it was immensely annoying) and finally today I got it fixed, this did the trick (note I have my git installed via scoop):
{ "autoUpdatesChannel": "stable", "env": { "SHELL": "C:\\Users\\redacted\\scoop\\apps\\git\\2.52.0\\bin\\bash.exe" } }Jetski5822 commentedon Jan 27, 2026
Omg, please fix this - its pretty much impossible to use Claude Code in a windows env with this popup.
I may need to try @DamianReeves workaround.
blueberrynotstraw commentedon Jan 29, 2026
I also suffer from this issue, and it makes Claude Code nearly unusable. I may give up usage of it entirely, or always run it on a 2nd desktop.
I experience this through CMD line Claude, as well Claude through VSCode
I had no luck attempting @DamianReeves 's workaround
Clinteastman commentedon Feb 4, 2026
This is so annoying. Constantly having terminal windows flashing up when I'm working is a pain. Can we please have a fix for this?
jarrieta86 commentedon Feb 10, 2026
Same issue here. Using Claude Desktop (not CLI) on Windows 11 with working directory on WSL (\wsl.localhost\Ubuntu\home...).
Every message triggers a visible /usr/bin/bash --login -i -c cygpath -u \wsl.localhost... window for git repo detection. The window flashes briefly and steals focus, making it very disruptive.
Attempted workarounds (none worked):
Setting SHELL to cmd.exe in ~/.claude/settings.json
Setting SHELL to powershell.exe
Removing SHELL env entirely
The windowsHide: true fix on child_process.spawn() would resolve this.
Environment:
Windows 11
Claude Desktop (latest)
Git for Windows installed (C:\Program Files\Git\cmd\git.exe)
Working directory: WSL Ubuntu via \wsl.localhost\
56 remaining items
cyneta commentedon Jul 30, 2026
Correction / retraction of my comment above.
On closer measurement, I was wrong to attribute the flashing to the statusLine and hook path on this version. Re-testing on v2.1.212 native (Windows 11):
windowsHide: trueon this build - all three spawn forms, including the statusLine path (spawn(cmd, [], {..., windowsHide: true})).EnumWindowswatcher polling every 30-40 ms over 1191 statusLinebash.exespawns in ~50 s (13 concurrent sessions) recorded zero new visible top-level windows of any class (ConsoleWindowClass/PseudoConsoleWindow).conhost.exeprocesses are created but no window ever becomes visible.So on 2.1.212 the statusLine/hooks are not the source of the visible flashing, contrary to my earlier comment.
The console spawns I can still see being created are stdio MCP servers, launched as:
(grandparent
claude.exe, allocating a fresh console0x4). If anyone is still getting flashes on a current build, worth checking whether thewindowsHideflag is being passed on the MCP stdio server spawn path specifically, as opposed to the statusLine/tool paths - they appear to be separate spawn sites. Repro is trivial: configure any stdio MCP server whosecommandisnpx(so it resolves throughcmd.exe) and watch for a console flash each time the server boots/reconnects.Happy to test a build with
windowsHideon the MCP spawn path.godefroi commentedon Aug 1, 2026
All the diagnostics I've done point to the statusline. I installed ccstatusline, got flashing windows; replaced ccstatusline with a custom powershell script, still get flashing windows. Disabled all my stdio MCPs (had Aspire and cwm-roslyn-navigator), still got flashing windows. Disabled my custom statusline altogether, and no more flashing windows.
JuanjoB-Viewnext commentedon Aug 3, 2026
Adding a data point that isolates this to Claude Code CLI's spawn of hook commands, not the plugin/tool being invoked.
What we ruled out downstream
I opened alexgreensh/token-optimizer#104 about the same flashing during normal work on Windows and tried three progressively more aggressive workarounds inside the plugin:
wscript //Bwrapper around every hook command — no visible reductionpythonw.exepreference in the plugin's own launcher (later landed upstream by the plugin author as_maybe_swap_to_pythonw) — no visible reduction eitherbash.exeentirely by rewritinghooks.jsonto invokepythonw run.pydirectly as the first token — no reduction (user reported it looked slightly worse)After the plugin author landed v5.11.72+ with the console-safe python swap and fixed a separate console-allocating Scheduled Task in the plugin itself, the observation on my Windows box was:
Since no downstream fix removed the per-tool-call flash, the console allocation must be happening in the
cmd.exe /c "<hook command>"wrapper that Claude Code CLI uses to spawn the hook — before any bash, python, or wscript inside it can matter. This is consistent with @mattgphoto's process sampling in #104 (111bash.exevs only 2cmd.exein 28s of agentic use, but the transientcmd.exe /cstill allocates a console).Concrete ask
If Node's
child_process.spawn(cmd, { shell: true, ... })for hook execution passedwindowsHide: true, thecmd.exe /cshim would still exist but its transient console wouldn't be shown. That's a one-line change on the CLI side and would eliminate the flash regardless of what the hook command does inside (bash, python, wscript, direct .exe).The plugin author documented this as an external limit in-source in v5.11.72+ (comment in #104).
Environment
shell: trueHappy to test a candidate build if one lands.
lnorton89 commentedon Aug 10, 2026
Reproduced this independently and want to share two findings that add some precision beyond "a console briefly appears" — one about why it can look like a full terminal window rather than a faint flash, and one about what's actually triggering it in a hook-heavy setup.
It's not always just a
conhost.exeflash — it can be a fullWindowsTerminal.exewindowIf
HKCU:\Console\%%StartuphasDelegationConsole/DelegationTerminalset to Windows Terminal's GUID ({2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}/{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}) — the default on many Windows 11 setups — then any unhidden console-allocating child process gets delegated to a brand-newWindowsTerminal.exe -Embeddingprocess, not just a bare conhost. That's a much more visually disruptive and confusing symptom than a "flash": it reads as an entire terminal application popping up and disappearing, with its own taskbar entry, which is probably why some reports in this thread describe "a program opening" rather than a flicker.I confirmed this is really happening (not just theorized) by polling window visibility directly via the Win32 API rather than only watching for process creation:
This caught multiple genuinely-new
WindowsTerminal.exe -Embeddingprocesses (fresh PIDs each time,IsWindowVisible = true, non-nullMainWindowTitle) appearing and disappearing within the same second-or-two window — i.e. directly-observed, not inferred from CPU/process churn.What's triggering it: PostToolUse
commandhooks, compounded across concurrent sessionsCross-referencing a WMI
__InstanceCreationEventprocess trace against the exact timestamps the visible windows appeared, the dominant activity at each flash was aPostToolUsehook (acommand-type hook registered insettings.json, e.g. one that runs after everyBashtool call) spawningbash.exe— sometimes as a nestedbash.exe -c "bash.exe ...script.sh"chain. Each spawn gets its ownconhost.exe.Two things made this much worse in my repro, which might explain why severity varies so much between reports:
Bash-matchedPostToolUsehook fires once per Bash call; a config with several matchers (Bash|Edit|Write|MultiEdit|Agent|Tasketc.) onPreToolUseandPostToolUsecan spawn 4–7+ child processes per single tool call.I could not pin the exact single spawn call missing
CREATE_NO_WINDOW/windowsHide— dozens of hook spawns fired per minute while only a handful produced a visible window, so whatever's happening is inconsistent rather than universally broken (i.e. most hook spawns are properly hidden; something about a subset isn't). But the mechanism — hook-command spawn → occasionally-visible window, worse with more hooks and more concurrent sessions, and rendered as a fullWindowsTerminal.exewindow rather than a faint flash when Terminal delegation is enabled — seems like a useful, reproducible angle for narrowing down the actual missing flag.Happy to share the full WMI trace / polling script if useful for a maintainer trying to reproduce this deterministically.
sorenssl commentedon Aug 10, 2026
Following up on my own comment above, and on @lnorton89's registry finding, with
a measurement of what that delegation setting actually costs.
Short version:
DelegationConsole/DelegationTerminaldoes not just change howthe flash looks. It decides whether a lingering console host costs ~2 MB or
grows unbounded. I have ~6.5 days of process-memory sampling across both
configurations on the same machine.
First, a correction to my own table above: I listed "Windows Terminal default
profile — not involved". That entry was about which profile Windows Terminal
opens, and it stands. The console delegation setting is a different mechanism
and it turns out to matter a great deal.
1. Claude Code does leave lingering console hosts (reproducing @cyneta's "25 hidden conhost")
Measured just now:
They accumulate and survive for days. On Console Host delegation this is
harmless — the 93-hour-old one holds the same 1.9 MB as the 46-hour-old ones,
so the host itself does not grow. That is the control for what follows.
2. On Windows Terminal delegation, the host grows ~320 MB/h for as long as the console lives
Same machine, earlier, when a console-allocating child was delegated to
WindowsTerminal.exe. Sampled every ~5 min by a memory watchdog writing CSV;recomputed from that raw CSV, two independent
WindowsTerminal.exeinstances:Two separate processes, near-identical rate. Growth is time-driven, not
output-driven: split into four 6-hour bands the rate is 316 / 365 / 320 / 321
MB/h regardless of whether anything was being printed. A buffer filling with
console output would be near zero on an idle night.
Effect on system memory pressure, same CSV:
Honest scoping — the trigger was mine, not Claude Code's. The process that
allocated that console was my own long-running service, launched with
CREATE_NO_WINDOW | DETACHED_PROCESS.DETACHED_PROCESSwins, the child gets noconsole, and when it later starts a console program Windows must create a fresh
one. Dropping
DETACHED_PROCESSmade theWindowsTerminal.exeprocess disappear,which is what pins the causality. This is the same "rule out your own software"
exercise as in my earlier comment, one layer deeper.
So I am not claiming Claude Code leaked 13 GB — its hook consoles are
transient. What I am claiming is that the two halves meet: Claude Code supplies
console hosts that linger for days (§1), and the delegation setting decides
whether such a host is a 2 MB conhost or a Windows Terminal process on a
~320 MB/h ramp (§2). On a default Windows 11 install pointing at Windows
Terminal, that combination is reachable without anyone doing anything unusual.
3. A mitigation that costs nothing while the flag question is settled
Setting the default terminal application to Windows Console Host caps the damage
when a spawn does get a visible console: a conhost flash instead of a full
WindowsTerminal.exe -Embeddingwindow with its own taskbar entry, and themeasured growth does not occur. Current state here, with Windows Terminal
1.24.11911.0 still installed:
(Settings → System → For developers → Terminal, or Windows Terminal's own
"Default terminal application" dropdown.)
It is a mitigation, not a fix. It makes the missing
windowsHidecheap instead ofexpensive, and does nothing about the flashing itself. The request in my earlier
comment stands.
Happy to share the watchdog CSV or the sampling script if that helps anyone
reproduce this deterministically.
Environment: Windows 11 Home 10.0.26200, Claude Code 2.1.207 (VS Code extension),
3 hooks configured (one
PostToolUseonEdit|Write, twoUserPromptSubmit), nocustom statusLine. Sampling window 2026-08-03 → 2026-08-10, 1919 samples.
godefroi commentedon Aug 10, 2026
@sorenssl I can't really tell if you're positing that WindowsTerminal.exe memory usage grows unbounded as a general rule, but it's worth pointing out that on my system, it doesn't. I have one process running on my machine that has been running around 9 days and has ~174MB WS and ~616MB PM. It's currently running 5 tabs, 3 of which have claude.exe running in them.
NiceLeader commentedon Aug 10, 2026
Confirming the Terminal-delegation mechanism from a different angle, plus a
narrower hypothesis for why only some hook spawns leak a window.
Short recap of what I posted in July, since it is the premise here: a
console-subsystem process gets its window created VISIBLE before it can hide
itself, so
powershell -WindowStyle Hiddenprovably still flashes on Win11 withTerminal delegation, while a GUI-subsystem (
winexe) binary spawned identicallydoes not.
On why it is inconsistent: nesting, not hook count
@lnorton89 notes that most hook spawns are hidden and only a subset leaks. My
data points the same way, and suggests the split is nesting.
On this machine I have two groups running side by side. Five hook entries whose
command is a single direct
node "<script>" <args>have never produced a window,across months. A plugin whose seven hook commands ran under
bashwithexport PATH="$($SHELL -lc 'echo $PATH' 2>/dev/null):$PATH"at the head producedthem constantly, including on
PostToolUsewithmatcher: "*", so once per toolcall. On Windows
$SHELLis/bin/bash.exe, so that prelude is a full loginshell spawned to read one variable.
That matches the
bash.exe -c "bash.exe ...script.sh"chains in your WMI trace.The plausible reading is that the runtime hides the process it starts, and the
leak is the grandchild the hook's own command line spawns, which nothing passes
CREATE_NO_WINDOWto.If that holds, the fix is narrower than "hide all hook spawns": a hook command
that spawns its own shell is outside the runtime's control, so either the flag
has to survive into the grandchild, or the hook has to be started in a way that
cannot allocate a console at all.
Two things follow for anyone hitting this now:
broken. The plugin above has a fix in flight (fix(windows): guard the login-shell PATH prelude behind a node check thedotmack/claude-mem#3519)
that drops the login shell when
nodeis already on PATH.symptom regardless of which spawn is at fault, since there is no console to
create. That has held for me across four consecutive releases of that plugin,
with the hooks still executing normally.
What I cannot answer
Same as @lnorton89: I never pinned the specific spawn site missing
CREATE_NO_WINDOW/windowsHide. The launcher approach sidesteps the questionrather than answering it, so this is a mitigation, not a diagnosis. A runtime fix
is still worth having.
Unrelated latency note, since hook count came up: running several handlers in one
process instead of one process per handler is worth doing on Windows for its own
sake. Node startup is around 75 ms here, so three separate handler spawns on
every
Writewere adding roughly 225 ms of pre-tool latency. That is aperformance fix, not a window fix.
sorenssl commentedon Aug 11, 2026
@godefroi Good push — and no, unbounded growth as a general rule is not what I
meant, and your 9-day instance shows it would be wrong. I ran a controlled test
overnight to find where the line actually sits. It turns out your case and mine
differ by one thing, and it is not the application.
Two phases, 75 minutes each, same machine, same night, 76 samples per phase at
60 s intervals.
Phase A behaves the way you describe: no measurable growth over 75 minutes
(the -1 MB/h is noise, not a decline), with handle count trending down,
929 -> 892.
Phase B pointed
DelegationConsole/DelegationTerminalat Windows Terminal andrecreated the chain behind my original numbers: a process started with
CREATE_NO_WINDOW | DETACHED_PROCESS(soDETACHEDwins and it gets no console),which then starts a console program — forcing Windows to create a fresh console,
which delegation hands to Windows Terminal.
The two obvious objections are both ruled out by the data:
292 MB/h. Straight line for 75 minutes, no flattening.
time.sleep(). It writes nothing atall. This matches the original incident, where the rate was essentially
constant across all four 6-hour bands of the day — 316 / 365 / 320 / 321 MB/h
— which is not the shape an output-driven buffer produces.
So the precise claim, narrower than the one I implied: Windows Terminal does
not leak as an application — it leaks in the role of console host for a process
that has none of its own. Same binary, two roles, and the entire difference
between them is the leak. You are looking at the first role. I was looking at
the second.
That distinction is what makes it relevant here rather than just my own bug.
Claude Code leaves console hosts parented to
claude.exealive for days — 8 onthis machine right now, aged 44 h to 116 h. Under Console Host they cost
1.9–2.5 MB each and do not grow with age: the 116-hour-old one holds 1.9 MB,
exactly what the 70-hour-old ones hold. Under Terminal delegation, that same
lingering host is on the ramp above. The registry value decides which of the
two you get.
One piece of credit I owe from my previous comment: @romemozu listed "switching
the default terminal from Windows Terminal to Console Host" among their
workarounds back on 6 July, a month before I presented the same switch as if it
were new. What I can add is why it is worth more than a cosmetic fix — the
numbers above are the difference between the two settings. Their caveat still
stands too: the internal
git/gh/tasklistspawns fromclaude.execannot be hidden from the user side at all. That fits what I see here — three
hooks configured, eight lingering hosts.
Scope, since one machine is one machine:
-Embeddingis thereasonable reading of what it was, not something I proved.
created by Windows. That is the point of the comparison, but it is more than a
one-variable difference.
And credit where it is due: your counter-example is what produced this. I had a
rate and no boundary, which is a claim rather than a finding — it took an
instance that demonstrably does not leak to turn "does Windows Terminal leak?"
into a question with an answer. One machine disagreeing with numbers was worth
more than agreement would have been.
The test is two files — a PowerShell harness that flips the setting, samples,
and restores it, plus the three-level process chain. I will post both if anyone
wants to run the same two phases elsewhere; a second machine would settle the
scope question faster than more measurements from mine.
sorenssl commentedon Aug 11, 2026
Correction to my previous comment.
I ended it with a claim my own test did not isolate: that Windows Terminal leaks
"in the role of console host for a process that has none of its own". The two
phases changed two variables at once — who created the Terminal process, and
whether the hosted process had a console of its own — and I attributed the whole
effect to the second.
Here is the missing control. Phase C: Terminal as the delegated console host
again, but hosting an entirely ordinary process, launched with
CREATE_NEW_CONSOLEfrom a parent that has its own console. NoDETACHED_PROCESS, no console-less parent anywhere in the chain.C matches B. The
DETACHED_PROCESSchain has nothing to do with it — it wasjust the path I happened to find this through. The qualifier in my sentence is
wrong, and the corrected claim is both simpler and broader:
Windows Terminal grows ~320 MB/h whenever Windows makes it the delegated
console host, regardless of how the hosted process was started. It does not grow
as a terminal you opened yourself.
That makes the delegation setting the entire variable, which if anything
sharpens the practical point in this thread: any console-allocating spawn that
is not hidden lands on that path when delegation points at Terminal — including
the hook and MCP spawns being discussed here.
Two notes on method, since I got one of them wrong:
Start-Processdid not create anew console, so no host was ever delegated. Because I logged conhost,
OpenConsole and WindowsTerminal separately with their parents, this showed up
as "no new console at all" rather than a result. Without that instrumentation
I would have reported "Windows does not delegate ordinary processes", which is
false.
than a leak: the chain ran once, one Terminal PID throughout, and the
per-minute deltas show no steps — 74 intervals, mean 5.26 MB, none above 3x
the mean. A scheduled console process on this machine did fire mid-phase; that
minute grew 6.1 MB against neighbours of 5.5, 5.5, 7.5 and 4.9. Thread count
fell from 92 to 86 across the phase. Adoption would show steps and rising
threads; neither is present.
Same machine and Windows Terminal version as before (Windows 11 Home 26200,
WT 1.24.11911.0), so the scope caveat stands: one box.
godefroi commentedon Aug 11, 2026
@sorenssl
Real talk here, drop your LLM for a second and turn on your brain. microsoft/terminal does have some reported "memory leak" issues, but every real reported issue of that type is tied to output volume, and the one you believe you've discovered here is over 2x the known worst-case output-driven scenario. If a bug like that existed, it'd be one of the most trivially reproducible, universally-triggered bugs in a massively-used, actively-staffed Microsoft repo (any detached process that allocates a console under default Windows 11 settings). It isn't filed anywhere.
marcodelpin commentedon Aug 11, 2026
Two independent data sets in this thread now point at the same spawn-path property, so here is a short synthesis for whoever picks this up.
bash -c "..."even when argv[0] is a plain.exe.bash.exeis a console-subsystem binary, so each dispatch materializes a conhost/Terminal window regardless of the child's own subsystem or any-WindowStyle Hiddenon the inner command.node "<script>" <args>never produced a window across months, while seven bash-wrapped hook commands flashed constantly. Same split, measured from the other side.The actionable slice for maintainers is still small: when a hook command's argv resolves to a single executable with no shell metacharacters, spawn it directly with
windowsHide: trueinstead of going through the bash wrapper; keepwindowsHideon the wrapper spawn for genuine shell commands. That removes the dominant flash source for native-binary hooks without touching the general Bash-tool path.NiceLeader commentedon Aug 12, 2026
I ran a controlled A/B on the wrapper question, since two readings of it were
circulating in this thread.
Setup: the same executable declared twice as a
PostToolUsehook in onesettings file, differing only in declaration form. Isolated headless session
(
claude -p --settings <file>), process creation watched viaWin32_Processwith parent resolution. Payloads write a marker file and then sleep, so nothing
is lost to sampling.
Both fired. Result:
bash.exeandlauncher.exeargslauncher.exeonlyclaude.exe, directly@marcodelpin's premise holds. A hook command written as a string is wrapped
in
bash -ceven when argv[0] is a plain.exewith no shell metacharacters.I had read a difference between two sets of hooks on my own machine as evidence
that the runtime inspects argv[0]. It does not. The difference was the
argsfield, and I was wrong about the cause.
What is new:
argsis an existing bypass. Declaringcommandplus anargsarray makes the runtime spawn the executable directly, with no shell inthe chain. That works today, with no runtime change, and it is why hooks
rewritten into that form have no bash parent. Any plugin whose hook command is
a fixed executable can convert to it now.
It also means the direct-spawn machinery already exists. The distance between
that and @marcodelpin's proposal is only the derivation step: deriving
argsautomatically when a string command has no shell metacharacters would route
those commands through a path that already works.
Scope note so this is not read as more than it is. This measures spawn shape,
not which spawn allocates the visible window. On this machine the bash wrapper
itself has never produced one, so my flashes came from further down the chain.
That may differ from the environment in @marcodelpin's measurements, and the
two questions are worth keeping separate.
NiceLeader commentedon Aug 17, 2026
A scoping correction to my Aug 10/12 comments, after trying to reproduce the
flash deliberately on this machine and failing in every configuration.
I ran the grandchild matrix from a hidden parent console:
bash -> node(2.5 s sleep),
bash -> node -> spawnSync(bun, {windowsHide:true}),bash -> node -> spawnSync(bun)with no flag at all, andspawn(bun, {detached:true, stdio:'ignore'})mirroring a daemon respawn.Window visibility polled via
IsWindowVisible, not process creation. Zerovisible windows in every cell, including the deliberately-unhidden ones.
Then I checked the one machine property @lnorton89 flagged:
HKCU:\Console\%%Startuphere is{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}-classic conhost delegation, not Windows Terminal. Under conhost delegation a
child of a hidden console inherits that console, so an unhidden grandchild has
nothing to pop.
Two consequences:
My earlier "the bash wrapper is hidden and never flashes here" observation
is delegation-scoped evidence, not a general result. On WT-delegation
machines (
{2EACA947-...}/{E12CFF52-...}) the same spawn shapes maybehave differently, and my machine cannot arbitrate which layer of the
chain allocates the visible window there.
Anyone posting "cannot reproduce" (me included, apparently) should attach
that registry value; a conhost-delegation machine may be structurally
incapable of the dramatic per-call
WindowsTerminal.exesymptom. One-liner:Get-ItemProperty "HKCU:\Console\%%Startup" | Select Delegation*For the record, one theory I tested and refuted rather than posted: a
detached:true, stdio:'ignore'daemon respawn from a hidden parent does NOTcreate a window here - libuv passes DETACHED_PROCESS on Windows, so the child
gets no console at all, despite the Node docs' "own console window" phrasing.
shangchuanqiytu-ui commentedon Aug 20, 2026
Confirming this reproduces on a vanilla install before any hooks or plugins are involved: with just Claude Code + Git Bash out of the box, every Bash tool call flashes its own console. The details below add a data point on how a hooks-heavy setup multiplies the frequency on top of that baseline.
Environment
node.exeObserved
Baseline (vanilla Claude Code, no hooks/plugins):
bash.exewith a visible console flash. Command output and exit status are unaffected — purely visual.With the hooks-heavy setup layered on top:
node.exethrough acmd.exewrapper, which allocates its own console. PreToolUse/PostToolUse match all tools, so a single tool call stacks 2–4 distinct flashes on top of the baseline (bash.exe+ hook spawns); a typical session (~40 tool calls) racks up 80+ flashes.Tried
settings.jsonoption appears to control window suppression for spawned children.Given what #27115 found (flashing persists even where
windowsHide: trueis set on the spawn call, pointing at thecmd.exewrapper layer), it looks like each spawn path needsCREATE_NO_WINDOWapplied consistently — or a single globalwindowsHide-style setting covering all child spawns (shell tools, hooks, statusline, snapshots, MCP stdio) would resolve this whole family at once.Happy to test any experimental build or flag on this machine.
rob-alexa commentedon Sep 4, 2026
Still reproducing on Windows 11 in the Claude desktop app (build 1.46388.2). The focus theft is the painful part: the window takes keyboard focus while I am typing, so characters land in a console that closes a second later.
Two additions:
commandplusargsflashes the same as a single command string.The workaround that fixed session start for me: point the hook at
pythonw.exe, a GUI-subsystem binary Windows allocates no console for, and have it spawn the real command withCREATE_NO_WINDOW. Two catches. Pass the std descriptors explicitly, orclose_fds=Trueleaves the child with invalid handles and its output is discarded silently. And propagate the child's exit code, or a PreToolUse guard's exit 2 stops blocking.Passing
windowsHide: truewhen spawning hooks would remove the need for any of this.Correction: an earlier version of this comment said structured-form hooks appeared not to flash. That was wrong.