fix: Embedded Claude panel: start on Windows, locate the CLI without a shell - #21
fix: Embedded Claude panel: start on Windows, locate the CLI without a shell#21lt3-me wants to merge 2 commits into
Conversation
The panel never got as far as the CLI on Windows. `spawnSession` located `claude` with `/bin/sh -c "command -v claude"` and started it inside a `/bin/bash -c <watchdog>` wrapper — neither shell exists there, so ConPTY answered «File not found» about bash, and the panel showed «не удалось запустить claude: Error: File not found:» whatever the state of the CLI. ### Locating the CLI `resolveCommand` replaces the shell lookup. A non-interactive sh reads no rc file, so its PATH was ours anyway; the walk is now done in node, along the SESSION's PATH (the one claude-env.json may extend — the header comment promised a custom PATH would work, and it did not reach the lookup), with PATHEXT on Windows. That is what finds every installer's launcher: npm's `claude.cmd` shim, the native installer's `claude.exe`, bun/pnpm/volta shims. CreateProcess on its own tries `.exe` and nothing else, and `where claude` lists npm's extensionless sh-script shim first — a file ConPTY cannot start. After PATH comes `~/.local/bin`, where the native installer puts the binary on every platform, for an editor launched from a desktop entry whose PATH has not caught up. A name with a separator is a path; `~` is expanded, as sh used to. A CLI that is not found is now reported by name, with the path of claude-env.json, instead of surfacing as the pty's error. ### Starting and stopping it On Windows the launcher is spawned directly, without the watchdog. The console is the watchdog there: the pseudoconsole is a handle of the Electron process, so a hard death takes the console host with it and every attached process receives CTRL_CLOSE_EVENT — verified with `taskkill /F` on the main process, claude.exe and its cmd.exe were gone within seconds. Closing the panel calls node-pty's ConPTY kill, which terminates the console's whole process list (claude and its MCP children); there are no process groups to signal. The Linux path — bash wrapper, SIGHUP then SIGKILL to the group — is unchanged. Environment overrides from claude-env.json are merged case-insensitively on Windows: a `PATH` next to the inherited `Path` would hand CreateProcess two spellings of one variable, and which one the child sees is not defined. One caveat, recorded in the code and in CLAUDE.md: a `.cmd` launcher is run by CreateProcess through cmd.exe, whose parser rewrites `%VAR%`, `^` and unquoted `& | < >`. The built-in arguments carry none of those — verified that the quotes and the non-ASCII arrows in the system hint reach claude.exe intact — and a user `args` override on Windows has to stay equally plain, or point `command` at the `.exe` the shim wraps. ### Verification On Windows 11, Electron 42, claude 2.1.265 from npm, over CDP: `claudeOpen` returns ok and the tree is cmd.exe → claude.exe with the arguments whole; `claudeClose` leaves no process behind; a hard kill of the Electron main process leaves no process behind; a bogus command name returns the new error text. `npm run typecheck` is clean. The Linux path was not exercised on this machine — the only change there is the lookup, and the e2e suites that set `command: "bash"` (e2e22–e2e34) cover it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lpFreedom#7 in view PR HelpFreedom#7 (simplaerai-sv, «Windows support for the embedded Claude session») solves the same failure on the v0.3 base. This compares the two and takes the one idea from it worth taking. ### Taken: the explicit cmd.exe route `winLaunch` now starts a `.cmd`/`.bat` launcher — npm's `claude.cmd` shim — through `%ComSpec%` itself instead of letting CreateProcess supply the interpreter. Two things come with that. `/d` skips the registry AutoRun (this machine has clink there; a conda hook is the other usual tenant), which otherwise runs inside the panel's cmd.exe and can print into it. And the quoting becomes ours: `/s` plus an outer pair of quotes around the whole line, which keeps a launcher path with a space in it whole. That last part is where PR HelpFreedom#7's version breaks. It spawns `cmd.exe /c "<shim>" args...` bare, and cmd's parser strips the first and the last quote of its command when the first character is a quote — so `"C:\Users\Jane Doe\AppData\Roaming\npm\claude.cmd" --mcp-config ...` becomes `C:\Users\Jane Doe\...` and cmd stops at `C:\Users\Jane`. A user name with a space is not exotic. Verified with a shim in a directory with a space: bare `/c` fails as described, the outer-quoted form and the implicit CreateProcess form both deliver every argument intact, inner quotes and non-ASCII included. ### Not taken: `where.exe` PR HelpFreedom#7 resolves the CLI with `where.exe` and prefers an `.exe`/`.cmd`/`.bat` hit, falling back to the first line — which is npm's extensionless sh shim, the file ConPTY cannot start. The PATH walk here uses PATHEXT, so that shim is never a candidate; it searches the SESSION's PATH, which claude-env.json may extend; and it checks `~/.local/bin`, the native installer's directory, for an editor launched from a desktop entry whose PATH has not caught up. It also spawns no helper process. Session teardown is the same in both: `pty.kill()` on Windows, node-pty's ConPTY kill enumerating the console's process list. ### Verification, on the real UI this time Windows 11, claude 2.1.265 from npm, the panel driven by hand: - open → one `cmd.exe /d /s /c "…claude.CMD …"` and one claude.exe; - close → nothing left; close-and-reopen in quick succession → exactly one tree; quit the editor with the panel open → nothing left; - `/mcp` inside the panel: kadr connected, 13 tools; the same 13 over a stdio client spawning mcp-bridge.cjs like claude does; kadr_state and kadr_eval answer; /eval refuses a request without the token and one carrying an Origin (403 both); - close/reopen regenerates kadr-mcp.json with a fresh bridge port; - the trust dialog that appears with no project open is the CLI's own: the panel starts in the home directory then, and the CLI does not persist trust for it even when run from a plain terminal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
How this differs from #7#7 (simplaerai-sv) fixes the same failure on the v0.3 base and was rebased onto Locating the CLI
Every way the CLI gets installed is found the same way: npm's Starting a
|
Problem
On Windows the Claude panel fails before it ever reaches the CLI:
Error: File not found:Installing or reinstalling
claudedoes not help, because the error is about/bin/bash, not about claude.electron/claude.tslocated the CLI with/bin/sh -c "command -v claude"and spawned it inside a/bin/bash -c <watchdog>wrapper;neither shell exists on Windows, so ConPTY reports the wrapper as missing.
What changes
CLI lookup moves from the shell into node (
resolveCommand). A bare nameis walked along the session's PATH — the one
claude-env.jsonmay extend,which the old lookup ignored — with PATHEXT on Windows. That finds every way
the CLI gets installed: npm's
claude.cmdshim, the native installer'sclaude.exe, bun/pnpm/volta shims. Plain CreateProcess would only find an.exe, andwhere claudelists npm's extensionless sh-script shim first,which ConPTY cannot start. After PATH the lookup checks
~/.local/bin, thenative installer's directory on every platform, for an editor launched from
a desktop entry whose PATH has not caught up with the install. Names with a
separator are taken as paths,
~is expanded.A missing CLI is reported by name, together with the path of
claude-env.json, instead of surfacing as the pty's "File not found".Windows spawns the launcher directly, without the watchdog. The console
takes the watchdog's role there: the pseudoconsole belongs to the Electron
process, so a hard death takes the console host down and every attached
process gets CTRL_CLOSE_EVENT. Closing the panel uses node-pty's ConPTY
kill, which terminates the console's whole process list, claude and its MCP
children included. There are no process groups to signal on Windows.
Env overrides merge case-insensitively on Windows, so a
PATHinclaude-env.jsonreplaces the inheritedPathrather than sitting next toit as a second spelling of the same variable.
The Linux path is untouched: same bash wrapper, same SIGHUP-then-SIGKILL to
the process group.
CLAUDE.mdgains a paragraph on all of the above.Verification
Windows 11, Electron 42, claude 2.1.265 installed from npm, driven over CDP:
claudeOpen→ok: true; process tree iscmd.exe → claude.exe, and the--append-system-promptargument reaches claude.exe intact, including theembedded double quotes and the non-ASCII arrows.
claudeClose→ no claude/cmd/node process left.taskkill /Fon the Electron main process → claude.exe and cmd.exe gonewithin seconds, no Electron processes left either.
KADR_CLAUDE_CMD=definitely-missing-cli→ the new error text with thecommand name and the config path.
npm run typecheckclean.Not exercised here: the Linux path. The only change there is the lookup
(
command -v→ PATH walk with identical semantics for a non-interactiveshell), and the suites that override
command: "bash"— e2e22, e2e23,e2e24, e2e31, e2e34 — cover it.
A run on Linux before merging would be welcome!!!
Known limits, deliberately out of scope
.cmdlauncher runs through cmd.exe, whose parser rewrites%VAR%,^and unquoted
& | < >. The built-in arguments avoid those; a userargsoverride on Windows has to as well, or
commandcan point straight at the.exethe shim wraps. Documented in the code and in CLAUDE.md.sweepStaleSessionsstill does nothing on Windows. The console lifetimecovers the claude tree; the ffmpeg export/proxy workers it also sweeps on
Linux are a separate task.
/opt/homebrew/bin; the resolver does not special-case it, since I couldnot test there.
🤖 Generated with Claude Code