렌탈 Mac mini에서 OpenClaw 게이트웨이 launchd 기동: node 절대 경로, ProcessType 및 종료 코드 78 수정 (2026-05-21)
재부팅 후 홍콩·일본·한국·싱가포르·미국의 렌탈 Mac mini M4에서 OpenClaw 게이트웨이 LaunchAgent가 관리 포트에 LISTEN하지 않거나, launchctl list에서 종료 코드 78로 종료되거나, 콜드 부트 후 약 3분 동안 WebSocket 클라이언트가 1006 비정상 종료를 보는 경우가 있습니다. 2026년 5월 21일 런북은 MCP 고아 프로세스가 아니라 launchd plist 오설정—ProgramArguments의 경로 없는 node와 누락된 ProcessType Interactive—을 다룹니다. 게이트웨이 재시작 복구, 헤드리스 SSH 최초 기동, Node 런타임 / nvm 정렬과 함께 읽으세요.
콜드 부트: 종료 코드 78, 느린 LISTEN, WebSocket 1006
Apple Silicon 현장에서는 launchd 장애가 두 가지 형태로 나뉩니다. 즉시 종료 78은 launchd가 게이트웨이 바이너리를 exec하지 못했다는 뜻입니다. ProgramArguments가 node로 시작하는데 launchd 환경의 PATH가 비어 있는 경우가 흔합니다. 기동 지연은 launchctl list에서는 실행 중인데 lsof로는 수 분간 LISTEN이 없고, 대시보드에 WebSocket 종료 코드 1006이 계속 기록되는 상태입니다. 둘 다 CPU를 때리는 ThrottleInterval 크래시 루프와는 다릅니다.
- 종료 상태 78:
launchctl bootstrap직후나 로그인 후—~/Library/LaunchAgents/*.plist에서 경로 없는node를 확인하세요. - 180초 이상: 유휴 mini에서 부팅부터 첫 헬스 프로브 성공까지 너무 오래 걸립니다.
- 1006: SSH와 디스크는 정상인데 제어 WebSocket만 끊김—아직 LISTEN하지 않은 상태이며 TLS 오설정이 아닙니다.
- SSH에서 수동
node gateway.js는 성공하지만 LaunchAgent 경로는 실패—전형적인 PATH 대 절대 바이너리 분리입니다.
node -v가 다르면 먼저 런타임 불일치를 읽으세요. 종료 78은 「바이너리를 찾지 못함」, 불일치는 「찾았지만 ABI가 다름」입니다.
launchd가 셸 PATH를 무시하고 백그라운드 에이전트를 낮은 우선순위로 두는 이유
LaunchAgent는 대화형 Terminal.app 세션보다 얇은 환경을 상속합니다. 문서와 현장 스레드는 plist의 EnvironmentVariables가 ProgramArguments 안의 인터프리터 이름 해석에 도움이 되지 않는다고 강조합니다. launchd는 해당 키를 적용하기 전에 실행 파일을 해석합니다. 그래서 노트북 plist를 node를 argv[0]으로 복사해도, 같은 XML에 PATH를 내보내도 헤드리스 ProxyMac mini에서는 실패합니다.
별도로 ProcessType이 생략되면 macOS가 재부팅 후 게이트웨이를 전력·스케줄링 휴리스틱의 백그라운드 워크로드로 취급할 수 있습니다. Label 옆에 <key>ProcessType</key><string>Interactive</string>을 추가한 뒤 콜드 부트 LISTEN 시간이 약 3분에서 수 초로 줄어든 운영 사례가 많습니다. 무인 GUI 세션 허가가 아니라 스케줄링 위생으로 보세요. 일회성 Keychain 작업은 최초 기동 체크리스트에 따라 VNC로, 이후에는 SSH를 유지하세요.
운영자 매트릭스 (신호 → 첫 조치)
| 주요 신호 | 첫 대응 (순서 중요) | 수집할 증거 | 오조작 시 롤백 | 담당 |
|---|---|---|---|---|
| OpenClaw 라벨의 마지막 종료 코드 78 | argv[0]을 $(command -v node) 절대 경로로 교체; bootout → bootstrap | launchctl print gui/$UID/<label> + plist XML | git에서 이전 plist 복원 | 플랫폼 SRE |
| 작업 실행 중이나 재부팅 후 >60 초 LISTEN 없음 | ProcessType Interactive 추가; 단일 plist 라벨 확인 | 타임스탬프가 있는 lsof -nP -iTCP:<port> -sTCP:LISTEN | 데스크톱 세션 정책상 금지면 ProcessType 제거 | 자동화 리드 |
| 관리 포트에 LISTEN 중복 | 단일 LISTEN 복구 따르기 | lsof 출력에 PID 2개 | 중복 라벨 bootout | 온콜 |
| <30 초 주기 크래시, CPU 고부하 | ThrottleInterval / KeepAlive 조정—본문 범위 밖 | log show --predicate 'process == "launchd"' --last 5m | throttle 키 되돌리기 | SRE |
9단계 plist 수정 (ProxyMac mini SSH)
- 라벨 식별:
launchctl list | grep -i openclaw로 전체 reverse-DNS 이름을 기록합니다. - 실시간 상태 출력:
launchctl print gui/$(id -u)/<label>로 마지막 종료 코드를 캡처합니다. - Node 해석: 같은 사용자 컨텍스트에서
command -v node(또는which node)로 절대 경로를 기록합니다. 보통/opt/homebrew또는~/.nvm아래입니다. - plist 편집:
ProgramArgumentsargv[0]을 해당 경로로 설정하고 게이트웨이 스크립트 경로도 절대 경로로 유지합니다. - ProcessType 추가: 콜드 부트 지연이 현장 보고와 일치하면 루트 dict에 Interactive를 넣습니다.
- XML 검증: 재로드 전
plutil -lint ~/Library/LaunchAgents/<file>.plist를 실행합니다. - 재적재:
launchctl bootout gui/$(id -u) <label>후 같은 경로로bootstrap(또는 벤더 kickstart)합니다. - time-to-listen: 5초 간격으로 120초 동안
lsof를 반복합니다. M4에서는 <15 초를 목표로 합니다. - 문서화: plist를 인프라 저장소에 커밋하고, 다음 담당자를 위해 런북에 본문 링크를 남깁니다.
/opt/homebrew/bin/node + openclaw-gateway 진입 스크립트 절대 경로 + --config + 설정 JSON 절대 경로—WorkingDirectory 없이 wrapper의 cd에 의존하지 마세요.
LISTEN, 헬스 엔드포인트, WebSocket 안정성 확인
bootstrap 후 설정된 관리 포트(운영 문서에서 흔히 18999—config.json과 맞추세요)에 정확히 하나의 PID만 LISTEN하는지 확인합니다. HTTP 헬스 경로가 있으면 curl하고, 데스크톱 클라이언트를 붙여 재부팅 후 30초 이내에 1006이 없는지 확인합니다. 헬스는 통과하는데 MCP 도구만 실패하면 게이트웨이 plist를 계속 고치지 말고 MCP 고아 위생으로 전환하세요.
자동화 호스트에서는 분기마다 한 번 재부팅 테스트를 권장합니다. launchd 퇴행은 당일 SSH 편집이 아니라 macOS 보안 업데이트 후에만 나타나는 경우가 많습니다. 티켓에 uname -r과 time-to-listen을 함께 기록하세요.
예방: IaC plist와 스테이징 라벨
- plist를 git으로 관리—Node 절대 경로는 이미지 빌드(Homebrew prefix 또는 nvm 기본값)에서 템플릿화합니다.
- dev/staging/prod용 LaunchAgent 라벨 분리—같은 mini에서 포트 충돌은 재시작 복구 가이드를 참고하세요.
- CI 스모크: 배포 후 SSH 스크립트로 <20 초 LISTEN을 확인한 뒤 호스트를 healthy로 표시합니다.
- HK/JP/KR/SG/US의 일회용 lab mini에서 plist 실험—프로덕션 오케스트레이터에서 디버깅하는 것보다 저렴합니다.
FAQ
OpenClaw LaunchAgent가 즉시 종료 코드 78로 종료되는 이유는? launchd는 EnvironmentVariables보다 먼저 ProgramArguments를 해석합니다. PATH가 비어 있으면 경로 없는 node 문자열은 실패합니다. command -v node의 절대 경로로 바꾼 뒤 bootout하고 plist를 다시 bootstrap하세요.
재부팅 후 게이트웨이가 LISTEN하기까지 수 분 걸리는 이유는? 기본 LaunchAgent plist에 ProcessType Interactive가 없으면 macOS가 백그라운드 기동을 수 분간 낮은 우선순위로 둡니다. 루트 dict에 Interactive를 추가하면 Apple Silicon mini에서 약 3분에서 수 초로 줄어든 사례가 많습니다.
ThrottleInterval 크래시 루프와 무엇이 다른가요? ThrottleInterval 문제는 CPU 고부하의 짧은 주기 재기동입니다. 종료 78은 게이트웨이 실행 전 일회성 설정 실패입니다. 크래시 루프 없이 LISTEN이 느리면 ProcessType이나 리소스 스케줄링을 의심하세요. KeepAlive와 잘못된 바이너리의 충돌이 아닙니다.
렌탈 Mac mini에서 OpenClaw launchd를 단단히 하는 이유
게이트웨이 plist는 인프라입니다. 재부팅, OS 업데이트, 노트북 Homebrew 경로만 아는 엔지니어를 견뎌야 합니다. Apple Silicon M4 mini는 예측 가능한 콜드 부트 타이밍을 주고, macOS launchd는 OpenClaw 문서의 LaunchAgent 흐름과 맞으며, HK / JP / KR / SG / US 배치로 제어 평면 지연을 자동화하는 API 근처에 둘 수 있습니다. ProxyMac에서는 스테이징 mini에 검증된 plist를 복제하고 SSH로 1분 미만 LISTEN을 증명한 뒤 같은 XML을 프로덕션으로 승격할 수 있습니다. 지역 선택은 요금, 접속 패턴은 도움말을 참고하세요.
스테이징에서 launchd plist 검증
HK / JP / KR / SG / US Mac mini로 OpenClaw 게이트웨이 기동을 단단히 하세요