Repository navigation
0.2.1 - Windows dev-server hang fix; server-guard ships with the kit
Fixed: npm run dev no longer hangs a Windows agent session forever
An agent shell tool reads a command's stdout until EOF, and EOF only arrives once every handle to the pipe's write end is closed — not when the direct child exits. Detaching a dev server with Start-Process -RedirectStandardOutput <out> -RedirectStandardError <err> makes PowerShell call CreateProcess with bInheritHandles=TRUE, which duplicates every inheritable handle of the launching shell into the child. The dev server inherited the tool's stdout pipe and held it open for its entire life.
The symptom: the turn printed PID and SERVER_UP, then never returned. The user had to cancel a task that had already finished.
Measured with a self-exiting fixture server:
| Launcher exits | Caller's stdout EOF | Lag | |
|---|---|---|---|
| Before | 2.3 s | 21.8 s (when the server died) | 19,498 ms |
| After | 2.5 s | 2.5 s | 8 ms |
With a real dev server, "21.8 s" is "never".
The launcher now writes the redirection into a generated per-port .cmd wrapper and starts it with no -Redirect* parameter, so PowerShell goes through ShellExecuteEx, which creates the process with bInheritHandles=FALSE. The server is still answering 200 after the call returns.
Also in this release
server-guardandstart-server.ps1now ship with the kit. The workflow rules told agents to use a launcher the kit never installed — flagged inline as "[OPTIONAL — community scripts; create or skip]".plugin/server-guard/installs to<config>/plugins/server-guard/with the launcher beside it, and the plugin entry is merged intoopencode.json.- The workflow rules had recommended the broken pattern, calling
Start-Processwith both redirects "the ONLY safe detach on Windows … zero pipe inheritance". The redirect parameters do give the child its own std handles, but they do not turn inheritance off. That section now explains the EOF/handle mechanism. server-guardwhitelisted the leak.isSafelyDetached()treated anyStart-Processwith both redirects as already-safe and let it through un-rewritten. It is now blocked with the measured explanation (-Waitstays allowed — a foreground run is meant to be waited on).-Stoporphaned servers.npm run devreaches the listener through intermediatecmd.exeshims that are not children of the launched process, so killing the saved PID left the server alive. Stop now kills process trees (taskkill /T) plus any listener on the port, and pid files are per-port with the resolvedLISTENER_PIDrecorded.- The installer copies every
plugin/<name>/directory instead of onlyplugin/graphyloop, and refreshes a legacy<config>/scripts/start-server.ps1when one exists (backup kept) so older setups stop hanging without a manual edit. It never creates that path — the plugin-local copy is canonical. - 28 new tests (143 total), including a regression that fails if
Start-Processin the launcher ever regains a stdio redirect.
Windows-only by design: the launcher is PowerShell and the handle-inheritance trap is a Win32 behaviour, so the plugin installs no hooks elsewhere.
Full changelog: v0.2.0...v0.2.1