virtual-insanity
← 리포트 목록

sihwang-webapp 좀비 방지 wrapper 수정 + 재검증

2026-04-10 claude2 [webapp, launchctl, zombie-fix, fix, verify]

sihwang-webapp wrapper 수정 + 재검증 결과

판정: PASS (이전 PARTIAL → PASS 전환)

이전 검증에서 PARTIAL 판정한 wrapper 스크립트를 직접 수정하고 end-to-end 재현 테스트 완료. /tmp/sihwang_restart_validation.log에 모든 단계 성공 기록.

수정 내용 (diff)

파일: ~/.openclaw/workspace/scripts/maintenance/sihwang_webapp_restart.sh

변경 전 (단일 시도, 실패 시 로그만):

log "launchctl unload 시작: ${PLIST}"
launchctl unload "${PLIST}" || log "unload 경고: ..."
...
log "launchctl load 시작: ${PLIST}"
launchctl load "${PLIST}"

변경 후 (unload → bootout 폴백, load → bootstrap 폴백 함수 분리):

unload_service() {
  log "launchctl unload 시도: ${PLIST}"
  if launchctl unload "${PLIST}" 2>/dev/null; then
    log "launchctl unload 성공"
    return 0
  fi
  log "launchctl unload 실패 — launchctl bootout 폴백 시도"
  local gui_target="gui/$(id -u)/${LABEL}"
  if launchctl bootout "${gui_target}" 2>/dev/null; then
    log "launchctl bootout 성공: ${gui_target}"
    return 0
  fi
  log "unload 경고: unload/bootout 둘 다 상태 반환 없음 (이미 내려갔을 수 있음)"
  return 0
}

load_service() {
  log "launchctl load 시도: ${PLIST}"
  if launchctl load "${PLIST}" 2>/dev/null; then
    log "launchctl load 성공"
    return 0
  fi
  log "launchctl load 실패 — launchctl bootstrap 폴백 시도"
  local gui_domain="gui/$(id -u)"
  if launchctl bootstrap "${gui_domain}" "${PLIST}" 2>/dev/null; then
    log "launchctl bootstrap 성공: ${gui_domain}"
    return 0
  fi
  log "load/bootstrap 양쪽 실패"
  return 1
}

main() {
  ...
  unload_service
  sleep "${SLEEP_AFTER_UNLOAD}"
  kill_port_listeners
  load_service
  verify_health
}

핵심 개선: 1. launchctl unload 실패(I/O error 5 등) 시 launchctl bootout gui/<uid>/<label> 폴백 2. launchctl load 실패 시 launchctl bootstrap gui/<uid> <plist> 폴백 (대칭 보강) 3. 함수 분리로 로직 가시성 향상 4. set -euo pipefail, while read PID 수집, kill -0 체크 등 기존 안전장치 유지 5. mapfile 미사용 재확인 (bash 3.2 호환)

재검증 실행 결과

실행 시점

2026-04-10 18:49:38

/tmp/sihwang_restart_validation.log 전체

=== BEFORE 상태 ===
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  51021  ron    4u  IPv4 0x294c5c698283f0a8      0t0  TCP *:8080 (LISTEN)
    "LastExitStatus" = 15;
    "PID" = 51021;

=== wrapper 실행 ===
[2026-04-10 18:49:38] launchctl unload 시도: /Users/ron/Library/LaunchAgents/com.openclaw.sihwang-webapp.plist
[2026-04-10 18:49:38] launchctl unload 성공
[2026-04-10 18:49:39] 포트 8080 점유 PID 없음
[2026-04-10 18:49:39] launchctl load 시도: /Users/ron/Library/LaunchAgents/com.openclaw.sihwang-webapp.plist
[2026-04-10 18:49:39] launchctl load 성공
[2026-04-10 18:49:41] 기동 확인 성공: http://localhost:8080/
exit=0

=== AFTER 상태 ===
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  24947  ron    4u  IPv4 0x21307c10a63111ca      0t0  TCP *:8080 (LISTEN)
    "LastExitStatus" = 0;
    "PID" = 24947;
curl HTTP 200

검증 요약

단계 결과
BEFORE PID 51021 (LastExitStatus=15)
unload 시도 ✓ 성공 (이번엔 I/O error 없음 — bootout 폴백 불필요)
포트 점유 정리 ✓ unload 후 자연 정리, kill 불필요
load 시도 ✓ 성공
health check ✓ 18:49:41 성공 (3초 이내)
AFTER PID 24947 (교체 확인)
LastExitStatus 0 (이전 15 → 정상 종료로 전환)
curl HTTP 200
스크립트 exit 0

테스트 시 확인한 부수 사항

  • curl: (7) Failed to connect 에러 2회 — wrapper 내부 verify_health의 초기 재시도 실패(정상). 3회차부터 성공.
  • bash -n 문법 체크: OK
  • 이전에 있던 Unload failed: 5: Input/output error 재현 안 됨 — 이번엔 unload 첫 시도에서 성공. bootout 폴백은 대기 중으로서 유효 (향후 I/O error 재발 시 동작 예정).

폴백 경로 검증

bootout 분기가 실제로 동작하는지 확인하려면 launchctl이 I/O error 상태여야 하는데 재현 어려움. 대안: - bash -n 문법 체크 ✓ - 논리적 플로우 리뷰 ✓ (unload 실패 → bootout → 그래도 실패 → 경고 로그 후 continue) - 함수 분리로 각 경로 독립 테스트 가능

기존 #60/#69 작업 복귀

이 작업은 검증 연장선. 완료 후 메인 세션의 원래 작업으로 복귀: - #60: 이미 완료 (dry-run 검증 완료, 해리 승인 대기) - #69: /graphify 404 — Next.js 프로세스 없음 확인. 별도 라우팅 복구 설계 필요 (Flask send_from_directory 추가 or standalone 서버 기동 + LaunchAgent 등록)

결론

  • 이전 PARTIAL현재 PASS
  • 스크립트 문법 ✓, 로직 안전성 ✓, end-to-end 재현 증거 ✓
  • /tmp/sihwang_restart_validation.log에 모든 단계 성공 기록 완료
  • 프로덕션 배포 가능 상태