# Watchdog 노이즈 제거 + 죽음 판정 정밀화 ★ Lv.3 critical

## 작업 레벨: Lv.3 critical (회장 강한 명시 승인)

## 회장 명시 (2026-05-03)
1. "요즘 와치독 발생 작업할 때마다 나타난다. 심층분석해서 개선해! 개판칠거면 하지마"
2. **"내가 원한건 있는 그대로를 체크하는거야. 첫 코딩은 끝났는데 PR 작업중이면 그 작업은 완료된건가? 아니잖아. 진짜 모든 검토가 끝나고 진짜 작업 완료라고 할 수 있을 때 완료해야지!!! 와치독은 봇이 죽어있는걸 찾아내는 기능이어야해. 엄연히 작업으로 돌고있는데 자꾸 와치독이 버그로 뜨니까 열받는거지"**

## 핵심 원칙 (회장 정의)
- **와치독 = "봇이 진짜 죽었는가" 검출 기능** (false stalled 절대 금지)
- **"완료" = .done + PR merged + 모든 QC PASS** (첫 코딩 끝 ≠ 완료)
- **PR 생성 중 / G3 검증 중 / 외부 도구 호출 중 = 정상 long-running** (알람 X)
- **"있는 그대로" 체크**: heartbeat만 보지 말고 PID + 단계 마커 + PR 상태 + worktree 교차 검증

## 진단 (아누 사전 분석 — 재현 완료)

### 버그 #1 — `no-taskfile` false positive (CRITICAL)
**증상**:
```
[2026-05-03 08:04:03] task-2389: task_file 없음 (memory/tasks/task-2389.md), 재위임 불가
```
**실제**:
```
$ ls -la memory/tasks/task-2389.md
-rw-rw-r-- 1 jay jay 8192 May  3 07:17 memory/tasks/task-2389.md  ← 존재
```
**원인 후보** (코드 분석 의무):
- 경로 상대/절대 혼선 (cwd 의존?)
- stat 또는 -f 검사 로직 결함
- task_id에서 task_file 매핑 함수 결함
- 위치: `scripts/session-watchdog.sh:339` 부근

### 버그 #2 — 동시 알람 = 노이즈 2배
**증상**: 같은 task에 `stalled-alert-only` + `no-taskfile` **둘 다** STALLED_LIST에 push → 4건처럼 보임 (실 2 task × 2 알람).
**위치**: `scripts/session-watchdog.sh:261`(stalled-alert-only) + `:339`(no-taskfile)
**fix 방향**: 한 task = 한 알람. no-taskfile은 stalled-alert-only의 sub-cause로 통합.

### 버그 #3 — `.escalate` 마커 무시 → 매 사이클 반복 알람
**증상**: task-2389는 07:41에 `.escalate` 발급. 그러나 watchdog이 매 2분 사이클마다 동일 task에 알람 (07:54, 07:56, 08:04 모두 동일 4건).
**fix**: `memory/events/<task_id>.escalate` 존재 시 알람 억제. `.escalate.acked` 마커는 회장 명시 승인 시점에만 정상 처리 재개.

### 버그 #4 — heartbeat 10분 기준 design 작업에 부적합
**증상**: design팀은 satori 다중 렌더 + L1 스모크 = 봇 컨텍스트 짧게 끊김. heartbeat 즉시 노후 → false stalled.
**fix 방향**:
- 작업 종류별 heartbeat 차등 (design = 30분, code = 10분)
- 또는 봇 G1/G2/G3 게이트 통과 시점에 heartbeat 강제 갱신

### 버그 #5 — 봇 점유 stale 표시 (whisper-briefing)
**증상**: bot-c (task-2389 종료 23분), bot-d (task-2391 종료 27분) 실제 free인데 whisper-briefing이 "봇점유" 표시. dispatch.py 봇 충돌 검사도 stale 점유 판정.
**fix 방향**: `memory/state/bot_status*.json` 또는 `bot_status_resolver` 로직과 협조하여 PID 종료 + heartbeat 노후 시점에 자동 해제 또는 watchdog이 resolver 호출.

---

### 버그 #6 — heartbeat-only 판정 = false stalled 폭증 (회장 핵심 통찰)
**증상**: heartbeat 10분 노후 = stalled 판정. 그러나 봇은 다음 단계(PR 생성/G3 검증/Codex CLI 호출 등) 진행 중일 수 있음.
**fix**: **AND 조건 죽음 판정**:
- (a) PID 없음
- (b) 작업 단계 마커 부재 (`<task>.codex-gate`, `<task>.qc-done`, `<task>.done.merging`, `<task>.g3-fail` 등 진행 신호 없음)
- (c) 마지막 events/ 파일 mtime N분 이상 정지
- (d) PR/worktree 상태도 변화 없음
- 위 4가지 모두 충족시만 죽음 판정. 1개라도 진행 신호 있으면 `alive (long-running)`.

### 버그 #7 — 정상 long-running 단계 화이트리스트 부재
**fix**: 다음 마커 존재 시 stalled 알람 절대 금지:
- `<task>.codex-gate` (Codex G1/G2 검증 중)
- `<task>.qc-done` (QC 진행 중)
- `<task>.done.merging` (G3 머지 진행 중)
- `<task>.pr-creating` (PR 생성 중) ← **신규 마커 도입**
- `<task>.external-running` (외부 CLI 호출 중) ← **신규 마커 도입**

### 버그 #8 — "완료" 정의 강제 (dispatch.py 봇 점유 검사도)
**fix**: 봇 free 판정 = `.done.acked` 발급 + PR merged + QC PASS 모두 충족. 첫 코딩 끝 / heartbeat만으론 free 판정 X. 단, escalate 발급 후 N시간 경과 시 "stuck" 별도 분류 (free 아님 + 알람 1회만).

### 버그 #9 — PR/worktree 교차 검증 의무
**fix**: heartbeat 노후 + PID 없음 시 무조건 stalled 판정 X. 다음 명령으로 진짜 진행 여부 확인:
- `gh pr list --search "<task_id>" --state all` (PR 생성됐고 review/머지 단계인가?)
- `git worktree list | grep <task_id>` (worktree 살아있는가?)
- 위 둘 모두 비어있고 (a)(b)(c)(d) 모두 충족시만 stalled 알람.

## 작업 범위

### Fix 1 — 버그 #1 (`no-taskfile` false positive) 근본 원인 + fix
- `scripts/session-watchdog.sh` 339 부근 task_file 존재 검사 로직 디버그
- 원인 후보 5개 모두 검증 (경로/stat/매핑/cwd/symlink)
- fix 후 회귀 테스트: 실 task md 8개 (task-2389~2396) 모두 watchdog이 "있음"으로 인식 검증

### Fix 2 — 동시 알람 통합 (한 task = 한 알람)
- `STALLED_LIST` 중복 push 방지
- no-taskfile은 stalled-alert-only의 reason 필드로 통합

### Fix 3 — `.escalate` 마커 시 알람 억제
- `memory/events/<task_id>.escalate` 존재 시 watchdog 사이클에서 skip
- `.escalate.acked` 마커가 있으면 정상 처리 재개
- **자동 acked 금지** (회장 승인만 .escalate.acked 생성 가능)

### Fix 4 — heartbeat 차등 + 작업 종류 인식
- `memory/tasks/task-XXXX.md` frontmatter 또는 metadata에서 작업 종류 추출
- design 작업: 30분 stale 기준
- code 작업: 10분 stale 기준 (기존 유지)
- 작업 종류 명시 부재 시 보수적 30분 적용

### Fix 5 — 알람 텔레그램 본문 디버깅 정보 추가
- 알람 텔레그램 보고 시 본문에 "task_file 존재함: yes/no" + "escalate 상태: yes/no" + "heartbeat last: <ts>" 포함

## 회귀 테스트 (12+ 시나리오)
**파일**: `tests/test_watchdog_noise_elimination.py` (신규)

1. 실 task-2389~2396 task md 8개 모두 "task_file 존재" 인식
2. 동시 알람 push 차단 (mock)
3. .escalate 마커 시 알람 skip
4. .escalate.acked 시 정상 처리 재개
5. design heartbeat 30분 차등
6. code heartbeat 10분 유지
7. heartbeat 노후 + .escalate 부재 + task_file 존재 → 정상 stalled-alert-only
8. heartbeat 노후 + .escalate 존재 → skip
9. 알람 텔레그램 본문에 reason + escalate 상태 + heartbeat last 포함
10. 4건 false alert 시나리오 재현 → fix 후 0건 확인 (regression)
11. STALLED_LIST 중복 push 차단 (단위 테스트)
12. 기존 `tests/test_session_watchdog.py` + `tests/test_session_watchdog.sh` 회귀 0

## 검증 시나리오 (회장 명시 4 목표 #1 무오류)
1. 12+/PASS (pytest + bash test)
2. **L1 실 운영 검증**: watchdog 사이클 30분 모니터링 → false alert 0건 확인
3. 회귀 0: dispatch.py / finish-task.sh / done-watcher.py / scripts/auto_e2e_gate.py 무수정
4. mypy/pyright 0 + shellcheck 0
5. 보고서에 4 버그별 fix 매트릭스 + L1 30분 모니터링 결과 의무

## affected_files

### 수정 (1개)
- `scripts/session-watchdog.sh`

### 신규 (2개)
- `tests/test_watchdog_noise_elimination.py`
- `memory/reports/task-XXXX-watchdog-noise.md`

### 변경 금지
- `dispatch.py`, `scripts/auto_merge.py`, `scripts/done-watcher.py`, `scripts/finish-task.sh`
- `scripts/whisper-compile.py`, `scripts/bot_status_resolver.py`
- `scripts/worktree_manager.py`, `scripts/cleanup_stale_task_counter.py`
- `scripts/auto_e2e_gate.py`, `scripts/motion_render_queue.py`, `scripts/ids_phase_monitor.py`
- `scripts/session_watchdog.py`, `scripts/orphan-watchdog.py`, `scripts/bot-status-watchdog.py`
- `skills/**`, `resources/design-md/**`
- `teams/shared/**`, `CLAUDE.md`
- `memory/capabilities/**`, `memory/audit/**`, `memory/state/**`, `.github/**`
- `memory/events/**` (90일 미만)

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "scripts/session-watchdog.sh"
    - "tests/test_watchdog_noise_elimination.py"
    - "memory/plans/tasks/task-XXXX/**"
    - "memory/reports/task-XXXX-watchdog-noise.md"
  forbidden_paths:
    - "dispatch.py"
    - "scripts/auto_merge.py"
    - "scripts/done-watcher.py"
    - "scripts/finish-task.sh"
    - "scripts/whisper-compile.py"
    - "scripts/bot_status_resolver.py"
    - "scripts/worktree_manager.py"
    - "scripts/cleanup_stale_task_counter.py"
    - "scripts/auto_e2e_gate.py"
    - "scripts/motion_render_queue.py"
    - "scripts/ids_phase_monitor.py"
    - "scripts/session_watchdog.py"
    - "scripts/orphan-watchdog.py"
    - "scripts/bot-status-watchdog.py"
    - "scripts/ids_natural_routing.py"
    - "skills/**"
    - "resources/design-md/**"
    - "teams/shared/**"
    - "CLAUDE.md"
    - "memory/capabilities/**"
    - "memory/audit/**"
    - "memory/state/**"
    - ".github/**"
  commands:
    - "pytest"
    - "bash"
    - "shellcheck"
    - "python3"
    - "git log"
    - "git diff"
    - "ls"
    - "stat"
    - "test"
  merge_policy: "tiered"
  ttl_hours: 24
```

## 작업 원칙
- **Think Before Coding**: 버그 #1 root cause부터 디버그 (코드 fix 전). 5 후보 검증 후 가장 가능성 높은 원인 명시.
- **3 Step Why**:
  - 1st Why: 왜 task md 존재인데 "없음" 판정? → 스크립트 검사 로직
  - 2nd Why: 왜 그 로직이 깨졌나? → 경로/stat/매핑/cwd/symlink 중 어느 것?
  - 3rd Why: 왜 회귀 테스트가 잡지 못했나? → 기존 테스트 커버리지 부족
- **Surgical**: session-watchdog.sh 1개 파일만 수정. 다른 watchdog 스크립트(session_watchdog.py, orphan-watchdog.py, bot-status-watchdog.py) 미수정.
- **Goal-Driven**: 4건 false alert → 0건. L1 30분 모니터링 통과.
- **No tokens waste**: 코드 1개 + 테스트 1개 + 보고서 1개. 신규 디렉토리 X.
- **재발 방지**: 회귀 테스트 12개로 5가지 버그 모두 카바.

## 운영
- ★ Lv.3 critical (회장 강한 명시: "개판칠거면 하지마")
- TTL 24h
- 위임: dev2-team (오딘, E2E 게이트 컨텍스트, bot-c)
- finish-task.sh 누락 금지

## 참조
- 회장 명시 2026-05-03 "심층분석해서 개선해! 개판칠거면 하지마"
- 기존 watchdog 작업 이력: `memory/meetings/2026-04-12-session-watchdog-v2.md`
- 회장 메모리 `bug_dispatch_after_reboot.md`
- 회장 4 목표 #1 무오류 (시스템 인프라 직접)
