Claude Code 세션 종료 후 백그라운드에서 CPU 자원을 과도하게 점유하는 고아 프로세스(Orphaned Process) 발생
10코어 머신에서 122 이상의 높은 로드 에버리지(Load Average) 기록, 팬 소음 및 시스템 성능 저하 유발
PPID 1을 가지며 수일간 실행된 `/bin/zsh` 프로세스가 문제의 원인으로 지목됨
`jobs -p` 명령어의 한계와 부모 쉘(Parent Shell)의 비정상 종료로 인해 자동 정리 실패
macOS의 `launchd`는 시스템의 데몬(Daemon) 및 서비스 관리자로서, 부모 프로세스가 종료되면 해당 프로세스의 자식 프로세스를 PID 1(`launchd`)에게 재귀적으로 재부모화(Reparenting)한다.
이로 인해 감독되지 않는 프로세스(Unsupervised Process)가 발생하며, 장시간 CPU를 점유해도 이를 인지하고 종료할 주체가 없어짐.
본 사례에서는 Claude Code 세션이 비정상 종료되면서, CPU를 고의로 점유하던 쉘 스크립트(Shell Script)들이 PID 1에 종속되어 방치됨.
`ps` 명령어의 `PPID` 필드를 확인하여 PID 1을 부모로 가지는 장기 실행 프로세스를 탐색하는 것이 문제 진단의 핵심이다.
문제의 원인 중 하나는 쉘 스크립트 내 `jobs -p` 명령어의 동작 방식에 있었다. 비대화형 쉘(Non-interactive Shell) 환경에서는 `jobs` 명령어가 작동하지 않아 백그라운드 작업 ID를 수집하지 못함.
`LOADPIDS=$(jobs -p)` 라인은 아무런 PID도 반환하지 못했고, 이후 `kill $LOADPIDS` 명령은 빈 인자로 실행되어 아무런 프로세스도 종료시키지 못함.
대안으로, 각 백그라운드 작업 실행 후 `$!` 변수를 사용하여 마지막으로 실행된 백그라운드 작업의 PID를 수집하고 이를 누적하는 방식이 권장됨.
또한, `trap` 명령어를 사용하여 `EXIT`, `INT`, `TERM` 시그널 발생 시에도 정리 로직이 실행되도록 보장하는 것이 안정적인 자원 회수에 필수적이다.
시스템의 CPU 부하 상태를 파악하기 위해 `uptime` 명령어의 로드 에버리지(Load Average) 값을 확인하는 것이 첫 단계이다. 이는 시스템의 평균적인 작업 큐 길이를 나타내며, 코어 수 대비 비정상적으로 높을 경우 문제의 심각성을 인지할 수 있음.
`ps -Ao pcpu,pid,ppid,user,comm -r`: CPU 사용률, PID, PPID, 사용자, 커맨드명을 기준으로 실시간 프로세스 목록을 정렬하여 보여줌.
`ps -o pid,lstart,etime,pcpu,args -p <PID>`: 특정 프로세스의 시작 시간(`lstart`), 경과 시간(`etime`), 전체 인자 목록(`args`)을 상세히 조회하여 실제 실행 중인 명령어를 파악하는 데 유용함.
`comm` 필드만으로는 `/bin/zsh`와 같이 모호한 정보만 제공되므로, 전체 인자 목록을 반드시 확인하여 실제 동작을 파악해야 함.
프로세스가 PID 1(`launchd`)에게 재부모화되는 상황은 일반적으로 부모 프로세스의 비정상 종료를 의미한다. 이는 시스템의 안정성을 저해하는 주요 원인 중 하나임.
`launchd`는 시스템 시작부터 종료까지 관리하는 핵심 프로세스로, 모든 프로세스의 최상위 조상(Ancestor) 역할을 수행함.
이러한 고아 프로세스(Orphaned Process)는 `nohup`이나 `screen`, `tmux` 등에서 분리되어 백그라운드로 실행되는 합법적인 데몬(Daemon)과 유사하게 보일 수 있어 주의가 필요함.
따라서 프로세스의 실행 인자(Arguments)를 면밀히 분석하여, 의도치 않은 자원 소모나 잠재적 위험을 가진 프로세스를 식별하는 것이 중요하다.
본 사례에서 CPU 집약적 작업 후 정리 실패는 두 가지 주요 요인이 복합적으로 작용한 결과이다.
1. 쉘 작업 제어(Job Control) 비활성화: 비대화형 쉘 환경에서 `jobs -p`는 유효한 PID 목록을 반환하지 못해 `kill` 명령이 무력화됨.
2. 부모 쉘의 조기 종료: CPU 점유율을 높이는 루프 실행 후 정리 로직에 도달하기 전에 부모 쉘 자체가 종료되어, 자식 프로세스들이 PID 1에게 재부모화됨.
이러한 문제를 방지하기 위해 백그라운드 작업 생성 시 PID를 명시적으로 수집하고, `trap` 명령어를 활용하여 프로세스 종료 시점에 정리 로직이 반드시 실행되도록 구현해야 함.
단순히 성공 경로(Happy Path)만 고려한 코드는 예외 상황에서 시스템 불안정성을 야기할 수 있다.