Spawning failures
ENOENT on spawn
Section titled “ENOENT on spawn”Two very different faults produce the same error code, and the message names whichever one Node hit first.
The binary was not found
Section titled “The binary was not found”Cause. A GUI-launched process inherits a minimal PATH — the desktop session’s, not the shell’s. nvm, volta and homebrew all live in directories added by shell init, so a CLI the user runs happily in a terminal is invisible.
Check.
import { AugmentedPath, LauncherFactory } from 'activecli';
console.log(new LauncherFactory().create().resolve('claude'));// null means it genuinely cannot be foundconsole.log(AugmentedPath.shared.toString());// confirm ~/.claude/local and friends are present
await command.version();// null is the quickest confirmation that the CLI cannot be run at allFix. Ensure the adapter spawns with augmentedPath.toEnv(options.env). If the install location is unusual, it belongs in BinDirectories.
The most common single case is claude migrate-installer, which puts the binary in ~/.claude/local and points a shell alias at it. A GUI-spawned backend never reads shell aliases, so without that directory on PATH the spawn fails.
The working directory does not exist
Section titled “The working directory does not exist”Cause. The cwd was a WSL UNC path handed to a process running inside the distro. That location does not exist there, so Node reports ENOENT for the directory — which reads like a missing binary.
Check.
const path = new PathParser().parse(projectRoot);path.isSpawnableAsCwd(); // false is the tellpath.toWslPath(); // what should have been passedFix. Do not pass a cwd that answers false. ClaudeAdapter omits it entirely in that case, which is why the CLI still launches — just in the wrong directory unless the caller converts it.
UNC paths are not supported
Section titled “UNC paths are not supported”Cause. cmd.exe refuses a UNC cwd outright. The agent then tends to pick the PowerShell tool instead of bash, which produces confusing downstream behaviour.
Fix. Same as above — convert with toWslPath(), or omit the cwd. WslUncPath.isSpawnableAsCwd() returns false on every host precisely because this fails from both directions.
See Paths for the full conversion rules.
An argument is silently altered on Windows
Section titled “An argument is silently altered on Windows”Cause. cmd.exe expands %VAR% even inside the double quotes Node adds. The argument reaches the launcher rewritten — or emptied, when the variable does not exist.
Behaviour. CommandRunner refuses rather than running:
Cannot run this command on Windows: argument #3 contains a '%' character,which cmd.exe expands as an environment variable (even inside quotes) andwould corrupt the value. Remove the '%' from that value and try again.Fix. Remove the percent sign from the value. There is no safe escaping — running a command whose arguments were silently rewritten is worse than not running it.
The error names the position but never echoes the value, because an argument may carry a token or a key. If you need to know which argument, count from the message’s index.
The resolved path is not the binary that runs
Section titled “The resolved path is not the binary that runs”Cause. On Windows, where claude lists every match including the extension-less MSYS wrapper npm ships beside claude.cmd. cmd.exe never runs that file — it resolves through PATHEXT.
Symptom. Install-method detection and update targets point at a file that is not what executes.
Fix. Resolve through WindowsLauncher, never by reading the first line of where output yourself.
new LauncherFactory().create().resolve('claude');Processes survive stop()
Section titled “Processes survive stop()”Cause depends on platform.
| Platform | Cause | Fix |
|---|---|---|
| POSIX | The CLI was not spawned detached, so it leads no process group and only the child itself was signalled |
Spawn with detached: true, as ClaudeAdapter does |
| win32 | git-bash workers detached from the parent-child tree, so taskkill /T cannot see them |
Ensure the spawn went through JobSpawner |
Check on win32:
new JobSpawner().isAvailable(); // false means the wrapper asset is missingWhen the wrapper is missing, JobSpawner falls back to a plain shell spawn. Everything appears to work — until a process orphans. If this returns false in a packaged build, the copy-assets build step did not run.
A third possibility on any platform: the process had already exited, and AbstractProcessTree.kill() refused to act. That refusal is correct — a reaped pid may have been reused, and signalling -pid or running taskkill /T on it would tear down an unrelated tree.
For what to do about the processes that got away regardless, see Orphans.