Skip to content

fix(hooks): hide console windows for detached spawns on Windows#1

Open
nboute wants to merge 9 commits into
fmflurry:mainfrom
nboute:fix/windows-hide-detached-spawns
Open

fix(hooks): hide console windows for detached spawns on Windows#1
nboute wants to merge 9 commits into
fmflurry:mainfrom
nboute:fix/windows-hide-detached-spawns

Conversation

@nboute

@nboute nboute commented Jul 2, 2026

Copy link
Copy Markdown

Problem

On Windows, every Write/Edit fires the PostToolUse hook, which spawns the reingest and cross-file resolver workers as detached child processes. Node's spawn(..., { detached: true }) opens a new console window for each child unless windowsHide: true is set. A burst of edits therefore produces a stream of flashing/persistent cmd windows.

Fix

Add windowsHide: true to the two detached spawns:

  • plugins/claude-code/scripts/lib/memory.jsspawnDetached() (reingest / resolve)
  • plugins/claude-code/scripts/on-post-tool.js — the resolver-debounce.js worker

windowsHide is a no-op on macOS/Linux, so there is no behavior change off Windows. Verified locally: after the change, editing files no longer opens console windows, and code-memory reingest still runs correctly (symbols + imports written to FalkorDB/Qdrant).

🤖 Generated with Claude Code

nboute-isagri and others added 6 commits July 2, 2026 13:49
On Windows, spawning a detached child process (`detached: true`) without
`windowsHide: true` opens a new console window each time. The PostToolUse
hook fires on every Write/Edit and spawns the reingest + resolver workers
detached, so a burst of edits produced a stream of flashing/persistent cmd
windows.

Add `windowsHide: true` to the detached spawns in:
  - scripts/lib/memory.js (spawnDetached — reingest/resolve)
  - scripts/on-post-tool.js (resolver-debounce worker)

No behavior change on macOS/Linux; the flag is Windows-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Installs the process-wide CREATE_NO_WINDOW default (_proc.py) in the CLI
and MCP server entry points, points the scheduled watcher task at
pythonw.exe so logon spawns stay windowless, and hides plugin hook
spawns in memory.js.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Any working docker engine is now accepted, resolved once per process:
native `docker` on PATH first (Docker Desktop keeps working), then
docker-ce inside WSL2 via `wsl -e docker` on Windows.

- new code_memory/_docker.py: resolution (native/wsl/daemon-down/none),
  wslpath translation for compose -f/--project-directory, WSL_UTF8,
  CODEMEMORY_WSL_DISTRO override, per-process cache
- updater.py: docker calls go through the resolved prefix; `docker
  inspect` switched from Go templates to plain JSON (wsl.exe argument
  re-parsing hazard); label compose paths passed verbatim daemon-side
- install.ps1: offers to provision docker-ce in the default WSL2 distro
  (systemd, get.docker.com, docker group, optional ONLOGON boot task)
  and verifies 127.0.0.1:6333 reachability from Windows
- install.sh: offers Colima (macOS, brew + compose cli-plugin symlink,
  brew services autostart) or docker-ce (Linux, get.docker.com); new
  --colima/--docker-ce flags for non-interactive installs
- contributor scripts: same resolution, neutral messages, JSON inspect
- README: new "Docker without Docker Desktop" section; .env.example
  aligned on 127.0.0.1 defaults

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`claude plugin list` prints a U+276F arrow and wsl/docker emit UTF-8;
under cp1252 the reader thread raised UnicodeDecodeError, leaving
stdout=None and crashing `update --check` on Windows. Decode UTF-8 with
errors=replace and export WSL_UTF8=1 so wsl.exe diagnostics stay
decodable too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WSL2 stops the VM about a minute after the last session detaches, even
with systemd services running — dockerd and the cm-* containers go down
with it and the runtime sees connection-refused. The logon task now
holds a hidden persistent session (`wsl.exe -e sleep infinity`) instead
of a one-shot boot, and the installer starts the same keepalive for the
current session. Verified: ports 6333/6379 stay reachable past the idle
timeout with the keepalive; without it the VM stopped within minutes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… schtasks

`schtasks /SC ONLOGON` requires an elevated shell ("Access is denied"
otherwise), which the one-liner installer cannot assume. A wscript .vbs
in the user Startup folder needs no elevation and launches the hidden
`wsl.exe -e sleep infinity` keepalive with window style 0 (no flash).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fmflurry pushed a commit that referenced this pull request Jul 8, 2026
Detached hook spawns, the pythonw watcher, the MCP server, and the
schtasks daemon each allocated a visible cmd window on Windows.

- JS: windowsHide on detached spawns; route spawnDetached through
  pythonw.exe when resolvable (_windowlessPythonw).
- Python: install_windows_no_window_default() wraps subprocess.Popen
  with CREATE_NO_WINDOW at the cli.py and mcp_server.py entry points.
- schtasks: register the LIVE daemon task (install_daemon /
  _daemon_task_xml) through a windowless pythonw command, not only the
  legacy per-repo install path. launchd/systemd unchanged (POSIX has
  no console allocation).

Reconstructed from PR #1 (fix/windows-hide-detached-spawns), rebased
onto v0.9.0, with the daemon-path gap fixed and tests added.
nboute-isagri and others added 3 commits July 8, 2026 16:18
The keepalive was only reachable through the docker-ce provisioning path,
so machines where docker already answers via `wsl -e docker` never got
it and lost dockerd to WSL idle-shutdown between sessions. Extract it
into Install-WslKeepalive (idempotent, offered once) and call it from
both the provisioning step and the plain wsl-resolution path; the
contributor script warns when the keepalive is missing. Windows-only
files — no behavior change for macOS/Linux installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Verify section told fresh installs to run `code-memory health`,
which the CLI has never shipped; point it at `update --check` and
describe the expected output (incl. the WSL variant).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Installs predating the 127.0.0.1 default ship ~/.code-memory/.env with
`localhost`, which Windows resolves to ::1 while the containers (and
WSL2 localhost forwarding) publish IPv4-only ports — every connection
is refused after switching engines. `code-memory update` now rewrites
the three exact legacy lines (backup kept as .env.bak); hand-edited
values and unix installs are left untouched. README gains a "Switching
engines" section: re-ingest as the simple path, optional volume
export/import to carry episodes over.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants