# 와치독 STALLED 판정 — heartbeat × 봇 PID 교차 체크

## Lv.2 작업

## 현재 문제
heartbeat만 보고 STALLED 판정 → 정상 종료된 작업도 재위임하거나, 봇 좀비 상태를 감지 못함.

## 수정
`/home/jay/workspace/scripts/session-watchdog.sh`

### STALLED 판정 로직 변경 (기존 150줄 부근)

현재:
```
heartbeat 없음 → 즉시 STALLED → /stop → 재위임
```

변경:
```
heartbeat 없음 → 봇 PID 체크
  → 봇 PID 있음 → "봇 좀비" → /stop → 3초 대기 → 재위임
  → 봇 PID 없음 → "정상 종료"
     → .done 있으면 → 아무것도 안 함 (skip)
     → .done 없으면 → task-timer end만 호출 (재위임 안 함)
```

### 봇 PID 체크 구현
```bash
# schedule_id에서 system_prompt 해시 추출
BOT_PID=""
if [[ -n "$SCHEDULE_ID" && "$SCHEDULE_ID" != "null" ]]; then
    # system_prompt 파일에서 해시 매칭
    PROMPT_FILE=$(grep -rl "$SCHEDULE_ID" /home/jay/.cokacdir/system_prompt_* 2>/dev/null | head -1)
    if [[ -n "$PROMPT_FILE" ]]; then
        PROMPT_HASH=$(basename "$PROMPT_FILE" | sed 's/system_prompt_\(.*\)_.*/\1/')
        BOT_PID=$(ps aux | grep "system_prompt_${PROMPT_HASH}" | grep -v grep | awk '{print $2}' | head -1)
    fi
fi

if [[ -z "$BOT_PID" ]]; then
    # schedule_id로 못 찾으면, 팀의 project_dir로 시도
    BOT_PID=$(ps aux | grep "claude.*-p.*${PROJECT_DIR}" | grep -v grep | awk '{print $2}' | head -1)
fi
```

### 분기 로직
```bash
if [[ "$HEARTBEAT_ALIVE" == "true" ]]; then
    # heartbeat 있음 → alive (기존 그대로)
    continue
fi

# heartbeat 없음 → 봇 PID 교차 체크
if [[ -n "$BOT_PID" ]]; then
    # 봇 좀비: heartbeat ❌ + 봇 PID ✅
    log "${TASK_ID}: 봇 좀비 감지 (heartbeat 없음, 봇 PID=${BOT_PID} 생존)"
    # /stop → 재위임 (기존 재위임 플로우 진입)
else
    # 정상 종료: heartbeat ❌ + 봇 PID ❌
    DONE_FILE="${WORKSPACE}/memory/events/${TASK_ID}.done"
    DONE_CLEAR="${WORKSPACE}/memory/events/${TASK_ID}.done.clear"
    if [[ -f "$DONE_FILE" || -f "$DONE_CLEAR" ]]; then
        log "${TASK_ID}: 정상 종료 확인 (.done 존재), 스킵"
        continue
    else
        log "${TASK_ID}: 정상 종료 (봇 PID 없음, .done 없음) → task-timer end만"
        /usr/bin/python3 "${WORKSPACE}/memory/task-timer.py" end "$TASK_ID" >> "$LOG_FILE" 2>&1 || true
        continue
    fi
fi
```

## 변경하지 않는 것
- heartbeat 있으면 alive (기존 그대로)
- circuit breaker, 백오프, 에스컬레이션 (기존 그대로)
- /stop 명령 전송 (task-1776.1에서 구현, 유지)
- 원인 분석 JSONL 기록 (task-1776.1에서 구현, 유지)

## 검증 시나리오
1. 정상 종료: heartbeat ❌ + 봇 PID ❌ + .done ✅ → 로그에 "정상 종료 확인" + 재위임 안 함
2. 정상 종료(done 없음): heartbeat ❌ + 봇 PID ❌ + .done ❌ → task-timer end만 호출 + 재위임 안 함
3. 봇 좀비: heartbeat ❌ + 봇 PID ✅ → 로그에 "봇 좀비 감지" + /stop 전송 + 재위임 진행
4. 정상 작업: heartbeat ✅ → "alive" 로그 + 스킵 (기존 그대로)
5. 기존 테스트 69건 회귀 없음

## 보고서
`/home/jay/workspace/memory/reports/task-{TASK_ID}.md`
