Files
airship/apps
Nayan 36facc6ba4 fix(cli): repair editor launch, process cleanup and port selection on Windows
Open-in-editor was broken outright, and took the daemon with it. All four
editors ship as `.cmd` shims on Windows and libuv's PATH search only tries
`.com` and `.exe` — so `where code` found the shim, onPath said yes, and the
spawn failed with ENOENT. spawn reports that asynchronously, so the try/catch
never saw it, and an `error` event with no listener throws: one click on a file
it could not open exited the server. The URL-scheme fallback was no better,
since it embedded a Windows path with backslashes, which are not legal in a URI
path. Both are fixed, and every detached spawn here now has an error listener.

`--exec` orphaned the dev server. The child is spawned with a shell, so on
Windows it is cmd.exe and the real tree is cmd.exe -> pnpm -> node -> Vite;
child.kill() maps to TerminateProcess on cmd.exe alone and left that tree
holding the port, outliving airship and blocking the next launch. taskkill /T
takes the descendants with it. Since that is already a forced kill, the
SIGTERM-then-grace-then-SIGKILL escalation is skipped there.

firstFreePort connect-probed, which answers "is anything accepting" rather than
"can I bind" — and the two diverge on Windows, where Hyper-V, WSL2 and Docker
Desktop reserve whole port ranges that accept nothing yet refuse to be bound.
It now binds and lets go. The residual case is handled properly too: listen()
had no error listener, so EADDRINUSE/EACCES became an uncaughtException that
escaped the caller's try/catch, leaving the dev server we started unstopped.

Also: opencode resolves through PATHEXT, because `npm i -g opencode-ai` — which
our own error message recommends — writes opencode.cmd, never opencode.exe; its
credential probe reads %LOCALAPPDATA%/%APPDATA% rather than XDG paths that are
never set on Windows; SIGBREAK is registered, since Windows never delivers
SIGTERM; and openInBrowser uses `cmd /c start ""` rather than a shell, where
Node does no argument escaping and a URL carrying `&` would be split in two.
2026-08-11 23:11:52 +05:30
..