프로세스 실행 실패
spawn 시 ENOENT
섹션 제목: “spawn 시 ENOENT”전혀 다른 두 결함이 같은 오류 코드를 냅니다. 메시지는 Node가 먼저 마주친 쪽을 지목합니다.
바이너리를 찾지 못한 경우
섹션 제목: “바이너리를 찾지 못한 경우”원인. GUI에서 실행된 프로세스는 최소한의 PATH를 상속받습니다. 셸의 PATH가 아니라 데스크톱 세션의 PATH입니다. nvm, volta, homebrew는 모두 셸 초기화가 추가하는 디렉토리에 있으므로, 사용자가 터미널에서 잘만 실행하는 CLI가 보이지 않습니다.
확인.
import { AugmentedPath, LauncherFactory } from 'activecli';
console.log(new LauncherFactory().create().resolve('claude'));// null이면 정말로 찾을 수 없다는 뜻입니다console.log(AugmentedPath.shared.toString());// ~/.claude/local 등이 포함되어 있는지 확인합니다
await command.version();// null이면 CLI를 아예 실행할 수 없다는 가장 빠른 확증입니다해결. 어댑터가 augmentedPath.toEnv(options.env)로 spawn하는지 확인하십시오. 설치 위치가 특이하다면 그 경로는 BinDirectories에 들어가야 합니다.
가장 흔한 단일 사례는 claude migrate-installer입니다. 이는 바이너리를 ~/.claude/local에 두고 셸 alias가 그것을 가리키게 합니다. GUI에서 spawn된 백엔드는 셸 alias를 읽지 않으므로, 그 디렉토리가 PATH에 없으면 spawn이 실패합니다.
작업 디렉토리가 존재하지 않는 경우
섹션 제목: “작업 디렉토리가 존재하지 않는 경우”원인. cwd가 WSL UNC 경로였고, 그것을 배포판 안에서 도는 프로세스에 넘긴 것입니다. 그 위치는 거기에 존재하지 않으므로 Node는 디렉토리에 대해 ENOENT를 보고하는데, 이것이 바이너리 누락처럼 읽힙니다.
확인.
const path = new PathParser().parse(projectRoot);path.isSpawnableAsCwd(); // false가 단서입니다path.toWslPath(); // 넘겼어야 할 값해결. false로 답하는 cwd는 넘기지 마십시오. ClaudeAdapter는 그런 경우 cwd를 아예 생략하며, 그래서 CLI는 여전히 실행됩니다 — 다만 호출자가 변환하지 않는 한 잘못된 디렉토리에서 실행됩니다.
UNC paths are not supported
섹션 제목: “UNC paths are not supported”원인. cmd.exe는 UNC cwd를 아예 거부합니다. 그러면 에이전트는 bash 대신 PowerShell 도구를 고르는 경향이 있고, 이는 이후에 혼란스러운 동작으로 이어집니다.
해결. 위와 같습니다 — toWslPath()로 변환하거나 cwd를 생략하십시오. WslUncPath.isSpawnableAsCwd()가 모든 호스트에서 false를 반환하는 이유가 바로 이것이 양방향 모두에서 실패하기 때문입니다.
전체 변환 규칙은 경로를 참고하십시오.
Windows에서 인자가 조용히 변조됨
섹션 제목: “Windows에서 인자가 조용히 변조됨”원인. cmd.exe는 Node가 붙이는 큰따옴표 안에서도 %VAR%를 확장합니다. 인자는 재작성된 채로 런처에 도달하며, 해당 변수가 없으면 아예 비워집니다.
동작. CommandRunner는 실행하는 대신 거부합니다.
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.해결. 값에서 퍼센트 기호를 제거하십시오. 안전한 이스케이프는 없습니다 — 인자가 조용히 재작성된 명령을 실행하는 것은 실행하지 않는 것보다 나쁩니다.
오류는 위치를 알려주지만 값은 절대 그대로 출력하지 않습니다. 인자가 토큰이나 키를 담고 있을 수 있기 때문입니다. 어느 인자인지 알아야 한다면 메시지의 인덱스를 기준으로 세십시오.
resolve된 경로가 실제 실행되는 바이너리가 아님
섹션 제목: “resolve된 경로가 실제 실행되는 바이너리가 아님”원인. Windows에서 where claude는 npm이 claude.cmd 옆에 함께 설치하는 확장자 없는 MSYS 래퍼까지 포함해 모든 일치 항목을 나열합니다. cmd.exe는 그 파일을 절대 실행하지 않으며, PATHEXT를 통해 해석합니다.
증상. 설치 방식 감지와 업데이트 대상이 실제로 실행되는 파일이 아닌 다른 파일을 가리킵니다.
해결. where 출력의 첫 줄을 직접 읽지 말고 WindowsLauncher를 통해 resolve하십시오.
new LauncherFactory().create().resolve('claude');stop() 후에도 프로세스가 살아남음
섹션 제목: “stop() 후에도 프로세스가 살아남음”원인은 플랫폼에 따라 다릅니다.
| 플랫폼 | 원인 | 해결 |
|---|---|---|
| POSIX | CLI를 detached로 spawn하지 않아 프로세스 그룹의 리더가 아니며, 자식 프로세스 자신에게만 시그널이 갔습니다 |
ClaudeAdapter가 그러하듯 detached: true로 spawn하십시오 |
| win32 | git-bash 워커가 부모-자식 트리에서 떨어져 나가 taskkill /T가 볼 수 없습니다 |
spawn이 JobSpawner를 거쳤는지 확인하십시오 |
win32에서의 확인:
new JobSpawner().isAvailable(); // false면 래퍼 asset이 없다는 뜻입니다래퍼가 없으면 JobSpawner는 평범한 셸 spawn으로 폴백합니다. 모든 것이 동작하는 것처럼 보이다가 — 프로세스가 고아가 될 때 드러납니다. 패키징된 빌드에서 이것이 false를 반환한다면 copy-assets 빌드 단계가 실행되지 않은 것입니다.
모든 플랫폼에 해당하는 세 번째 가능성: 프로세스가 이미 종료되어 AbstractProcessTree.kill()이 동작을 거부한 경우입니다. 그 거부는 옳습니다 — 회수된 pid는 재사용되었을 수 있고, 그 pid에 -pid 시그널을 보내거나 taskkill /T를 실행하면 무관한 트리를 무너뜨립니다.
그럼에도 빠져나간 프로세스를 어떻게 할지는 고아 프로세스를 참고하십시오.