# 세션 자동 이어가기 Watchdog — Phase 1 구현

## Lv.3 작업

## 설계 문서 (반드시 먼저 읽을 것)
- 계획서: `/home/jay/workspace/memory/plans/session-watchdog/plan.md`
- 맥락노트: `/home/jay/workspace/memory/plans/session-watchdog/context-notes.md`
- 체크리스트: `/home/jay/workspace/memory/plans/session-watchdog/checklist.md`
- Agent Meeting v2: `/home/jay/workspace/memory/meetings/2026-04-12-session-watchdog-v2.md` (offset/limit 사용)

## 구현 범위

### 1. post-tool-use.sh 수정 (1줄 추가)
파일: `/home/jay/.claude/hooks/post-tool-use.sh`

기존 task_id 읽기 로직 이후에 heartbeat 갱신 추가:
```bash
# heartbeat 갱신 (session-watchdog용)
[[ -n "$TASK_ID" ]] && date +%s > "/home/jay/workspace/memory/heartbeats/${TASK_ID}.heartbeat" 2>/dev/null
```

- `memory/heartbeats/` 디렉토리 사전 생성 (mkdir -p)
- 기존 hook 동작에 영향 없음 확인 필수

### 2. session-watchdog.sh 작성
파일: `/home/jay/workspace/scripts/session-watchdog.sh`

**판정 로직 (2중 확인):**
```
for each running task in task-timers.json (anu-direct 제외):
  1. .done 파일 존재? → pass (정상 완료)
  2. PID 추적 (Primary):
     - task-timers.json의 schedule_id 읽기
     - grep "$SCHEDULE_ID" ~/.cokacdir/system_prompt_* → system_prompt 파일
     - 파일명에서 hash 추출 → ps aux | grep hash → PID 확인
     - PID 존재 → alive, pass
  3. heartbeat mtime (Secondary):
     - memory/heartbeats/{task_id}.heartbeat 의 mtime 확인
     - mtime < 10분 → alive, pass
  4. 둘 다 없음 → stalled 판정
     - retry_count < max_retry(2) → dispatch.py 재위임 호출
     - retry_count >= max_retry → Telegram 알림 (에스컬레이션)
```

**PID 추적 경로 (실측 확인됨):**
```bash
# schedule_id로 system_prompt 파일 찾기
PROMPT_FILE=$(grep -l "$SCHEDULE_ID" ~/.cokacdir/system_prompt_* 2>/dev/null)
if [ -n "$PROMPT_FILE" ]; then
  HASH=$(basename "$PROMPT_FILE" | sed 's/system_prompt_\(.*\)_.*/\1/')
  PID=$(ps aux | grep "system_prompt_${HASH}" | grep -v grep | awk '{print $2}')
  if [ -n "$PID" ]; then
    # 세션 살아있음
  fi
fi
```

**재위임 호출:**
```bash
unset CLAUDECODE
/usr/bin/python3 /home/jay/workspace/dispatch.py \
  --team "$TEAM" \
  --task-file "$TASK_FILE" \
  --level normal \
  --type coding \
  --override-routing \
  --skip-qc-gate
```

**알림:**
```bash
# 환경변수에서 토큰 로드 (ps aux 노출 방지)
source /home/jay/workspace/.env.keys
curl -s -X POST "https://api.telegram.org/bot${ANU_BOT_TOKEN}/sendMessage" \
  --data-urlencode "chat_id=6937032012" \
  --data-urlencode "text=⚠️ [Watchdog] task-${TASK_ID} stalled — 재위임 ${RETRY_COUNT}/${MAX_RETRY}" \
  > /dev/null
```

**주의사항:**
- 모든 변수 따옴표 처리 (`"$VAR"`)
- 절대경로 사용
- 로깅: `/home/jay/workspace/logs/session-watchdog.log`
- anu-direct 작업은 무조건 제외
- heartbeat 파일 퍼미션 600

### 3. systemd unit 파일
기존 done-watcher.timer 패턴 참조: `systemctl --user cat done-watcher.timer`

**session-watchdog.service:**
```ini
[Unit]
Description=Session Watchdog - 팀장 봇 세션 자동 이어가기

[Service]
Type=oneshot
EnvironmentFile=/home/jay/workspace/.env.keys
ExecStart=/bin/bash /home/jay/workspace/scripts/session-watchdog.sh
StandardOutput=append:/home/jay/workspace/logs/session-watchdog.log
StandardError=append:/home/jay/workspace/logs/session-watchdog.log
```

**session-watchdog.timer:**
```ini
[Unit]
Description=Session Watchdog Timer (2분 주기)

[Timer]
OnUnitActiveSec=120s
AccuracySec=10s

[Install]
WantedBy=timers.target
```

등록: `systemctl --user enable --now session-watchdog.timer`

### 4. task-timers.json 스키마 확장
dispatch.py가 반환하는 `schedule_id`를 task-timers.json에 기록하도록 dispatch.py 수정:
- `schedule_id` 필드 추가 (cron_response.id 값)
- `retry_count` 필드 추가 (default 0)
- `max_retry` 필드 추가 (default 2)
- `task_file` 필드 추가 (dispatch 파일 경로)
- 기존 파싱 로직 하위 호환 확인

### 5. 재위임 시 서브넘버링
task-1707 → task-1707.2 → task-1707.3 형태로 서브 task_id 생성.
dispatch.py의 `--task-id` 옵션 사용.

## 테스트
- TC1: heartbeat 갱신 후 watchdog → 재위임 안 함
- TC2: heartbeat 10분 초과 + .done 없음 + PID 없음 → 재위임 실행
- TC3: .done 존재 → 재위임 안 함
- TC4: PID 존재 + heartbeat 10분 초과 → alive (PID가 우선)
- TC5: retry_count=2 → 재위임 차단 + 알림
- TC6: anu-direct 작업 → watchdog 대상 제외
- TC7: post-tool-use.sh 기존 기능 회귀
- TC8: systemd timer 정상 동작

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