# task-2670 — PR_WATCHER_TERMINAL_CALLBACK_ROOT_CAUSE_PACKET

- Level: Lv.3 (★ root cause + fix packet only · 코드 수정 0 · runtime 변경 0 · commit/push/PR/merge 0)
- 담당: **dev8 라** (★ task-2666 COMPLETED 후 슬롯 자유 · task-2662~2669 작성자 충돌 회피 · 결함 봇 dev7 자기 분석 모순 회피)
- base: origin/main 최신 (HEAD `2752182a`)
- 단일소스: 본 task md + dev7 watcher poll_history 30 + schedule_history + memory feedback_anu_no_direct_ci_watch_use_handoff_260523
- chair_authorization_id: **`CHAIR-AUTH-PR-WATCHER-TERMINAL-CALLBACK-ROOT-CAUSE-20260525-JJONGS-RCA-001`** (★ 회장 verbatim 발급 완료 2026-05-26)

## 배경

PR #149 watcher (task-2667 dev7 이참나 · schedule 29C74592) 사고 **정정 (회장 verbatim 2026-05-26)**:
- poll 30/30 완료 · t_offset 3524s (≈58.7min · max_watch_minutes=60 도과)
- poll #12 (t_offset 1336s ≈22.27분) 시점: fresh Gemini review for head 4bb627fe 도착 + **3 new unresolved threads 감지**
- terminal_state=**LOOP_BOUNDARY** (★ ANU normal callback **도착** 검증 · schedule_history 29C74592.log status=ok)
- watcher process 자체 종료 (★ 60min 도과 후 LOOP_BOUNDARY 분기)

**★ RCA 주제 (회장 verbatim 정정)**:
"poll #12에서 fresh unresolved 3개가 발생했는데 왜 HOLD_FOR_CHAIR로 조기 전환하지 않고 max_watch 종료 후 LOOP_BOUNDARY로 갔는가"

회장 verbatim 2026-05-25/26: "즉시 코드 수정이 아니라 root cause + fix packet 먼저 작성".

## 회장 verbatim 결정 (1:1)

- 별도 task 추진 (★ 본 task)
- 범위: root cause + fix packet only (★ 즉시 코드 수정 0)
- 완료 상태: `PR_WATCHER_TERMINAL_CALLBACK_ROOT_CAUSE_PACKET_READY`

## 허용 9 (root cause + fix packet only)

1. `memory/specs/pr_watcher_terminal_callback_root_cause_*.md` 작성
2. `memory/events/task-2670.*` 작성
3. `memory/reports/task-2670.md` 작성
4. **watcher.py 코드 read-only 분석** (★ /home/jay/.cokacdir/workspace/29C74592/watcher.py)
5. **poll_history 30 + _history.json read-only 검토**
6. **schedule_history 29C74592.log 부재 분석** (★ schedule_history 로그 0 의미 평가)
7. **terminal_state evaluation logic 재검토** (★ poll_30 시점 BLOCKED + unresolved=3 + fresh evidence 도착 시 5 enum 중 어떤 분류 적용 여부)
8. **callback registrar 호출 path 추적** (★ watcher → cokacdir --cron 호출 코드 위치 / 호출 누락 시점)
9. **fix packet 작성** (★ LOOP_BOUNDARY 분기 / terminal_state 5 enum evaluation / callback registrar 호출 / max_watch_minutes 도달 시 finalize)

## 금지 8 (회장 verbatim 1:1)

1. 즉시 코드 수정 (★ utils/** / hooks/** / watcher.py 변경 0)
2. PR 생성
3. merge
4. dev bot 재dispatch
5. live settings.json 변경
6. dispatch.py 변경
7. hooks live 변경
8. Axis runtime / HARNESS_ENFORCED / RUNNING 자동 선언

## 완료 상태 (회장 verbatim)

- 성공: **`PR_WATCHER_TERMINAL_CALLBACK_ROOT_CAUSE_PACKET_READY`**

## 검토 후보 4 (회장 verbatim 2026-05-26 정정)

1. **fresh unresolved 발생 시 HOLD_FOR_CHAIR 조기 전환 조건 미흡**: poll #12 시점 unresolved=3 + BLOCKED + fresh Gemini head match 검출됐는데 즉시 HOLD_FOR_CHAIR terminal로 분류하지 않음 (★ ANU 자동수렴 trigger 시점 지연)
2. **terminal_state 5 enum evaluation policy 결함**: 매 poll 시점 5 enum 평가 logic 결함 (★ BLOCKED + fresh evidence + new unresolved 조합에 대한 분기 부재)
3. **unresolved callback timing policy 결함**: unresolved thread 발생 시점 callback 발사 timing 정책 미정의 (★ max_watch 종료 시점까지 대기 vs 즉시 보고)
4. **max_watch_minutes 도달 전 HOLD 조건 누락**: LOOP_BOUNDARY로 자연 종결되기 전 단계에서 HOLD 전환 우선순위 정책 부재

## 보고 필수 7

1. **root cause classification** (★ 위 4 후보 중 단일/복합 식별)
2. **watcher.py code path 인용** (★ 결함 line 번호 + verbatim quote)
3. **terminal_state 분류 표 (poll_30 시점)** (★ BLOCKED + unresolved=3 + fresh Gemini head match → 정상 분류 결정)
4. **callback registrar code path 인용** (★ 어디서 호출되어야 했는지)
5. **fix packet diff preview** (★ 실제 diff 0 · preview only)
6. **regression suite 제안** (★ 본 사고 재발 방지 test 3 케이스: LOOP_BOUNDARY / fresh-evidence-with-new-unresolved / callback-registrar-failure)
7. forbidden_action_count (target 0)
8. recommended next action (★ fix 적용은 본 packet 박제 후 별도 회장 verbatim 강제)

## expected_files (~6 · Track G 전용)

- `memory/specs/pr_watcher_terminal_callback_root_cause_packet_260525.md` (★ 통합 packet)
- `memory/specs/pr_watcher_terminal_callback_root_cause_analysis_260525.md` (★ root cause 분석)
- `memory/specs/pr_watcher_terminal_callback_fix_diff_preview_260525.md` (★ preview only)
- `memory/specs/pr_watcher_terminal_callback_regression_suite_260525.md` (★ test 케이스)
- `memory/events/task-2670.done`
- `memory/events/task-2670.pr-watcher-terminal-callback-rca-result-260525.json` (★ 보고 8 필드)
- `memory/reports/task-2670.md`

## allowed_resources

```yaml
allowed_resources:
  paths:
    - "memory/specs/pr_watcher_terminal_callback_*.md"
    - "memory/events/task-2670.*"
    - "memory/reports/task-2670.md"
    - "memory/tasks/task-2670.md"
    - "memory/system/.callback_ledger.jsonl"
    - "memory/.callback_inbox/**"
    - "INDEX.md"
  read_only_reference:
    - "/home/jay/.cokacdir/workspace/29C74592/watcher.py (★ dev7 watcher code · read-only 분석)"
    - "/home/jay/.cokacdir/workspace/29C74592/poll_history/poll_*.json (★ 30 polls)"
    - "/home/jay/.cokacdir/workspace/29C74592/poll_history/_history.json"
    - "memory/specs/system_ci_watch_handoff_policy_spec_260523.md (★ 12 필드 + 5 terminal_states doctrine)"
    - "memory/feedback_anu_no_direct_ci_watch_use_handoff_260523.md (★ CI_WATCH_HANDOFF doctrine)"
    - "memory/events/pr-149-ci-gemini-watch-handoff-dispatched-task-2667-260525.json (★ task-2667 dispatch marker)"
    - "memory/events/authorized-gemini-review-trigger-required-pr-149-head-4bb627fe-260525.json (★ ANU /gemini review nudge 박제)"
  forbidden_paths:
    - "/home/jay/.claude/settings.json"
    - "/home/jay/.claude/hooks/**"
    - "/usr/local/bin/cokacdir"
    - ".github/**"
    - "hooks/**"
    - "dispatch.py"
    - "dispatch/**"
    - "scripts/finish-task.sh"
    - "utils/**"
    - "tests/**"
    - "schemas/**"
    - "/home/jay/.cokacdir/workspace/29C74592/watcher.py (★ read-only · 수정 0)"
    - "memory/specs/v3_1_*.md (★ task-2668 파일)"
    - "memory/specs/v3_6_*.md (★ task-2664/2665/2669 파일)"
    - "memory/specs/backlog_*.md (★ task-2666 파일)"
    - "memory/tasks/task-2662*"
    - "memory/tasks/task-2663*"
    - "memory/tasks/task-2664*"
    - "memory/tasks/task-2665*"
    - "memory/tasks/task-2666*"
    - "memory/tasks/task-2667*"
    - "memory/tasks/task-2668*"
    - "memory/tasks/task-2669*"
    - "**/.env*"
    - "**/credentials*"
  commands:
    - "python3 -m py_compile"
    - "python3 -m json.tool"
    - "python3 -c"
    - "ls"
    - "cat"
    - "stat"
    - "sha256sum"
    - "wc"
    - "printf"
    - "grep"
    - "find"
    - "tail"
    - "head"
    - "cokacdir --cron"
    - "cokacdir --currenttime"
    - "touch"
    - "mkdir"
  merge_policy: "rca_packet_only_no_runtime_change_no_pr"
  ttl_hours: 48
```

## frozen anchors

- ANCHOR-1: "root cause + fix packet only · 즉시 코드 수정 0 · watcher.py 변경 0"
- ANCHOR-2: "dev7 watcher 자기 분석 모순 회피 · executor dev8 라 (★ task-2666 COMPLETED 후 슬롯 자유)"
- ANCHOR-3: "watcher.py read-only 분석 + poll_history 30 + _history.json 검토"
- ANCHOR-4: "fix 적용은 본 packet 박제 후 별도 회장 verbatim 강제 · 본 task는 RCA + fix preview only"
- ANCHOR-5: "task-2662~2669 파일 충돌 0 · 본 task prefix `pr_watcher_terminal_callback_*` 차별화"

## finalize 프로토콜

1. base = origin/main (HEAD `2752182a`)
2. 별도 worktree task-2670-dev8
3. dev8 라 봇 작업 (★ RCA + fix packet 작성)
4. 4 sub-spec (RCA / fix diff preview / regression suite / packet) + result + report
5. dev7 watcher.py + poll_history read-only 분석
6. result marker (보고 8 필드)
7. ANU normal callback cron (helper · ANU key `c119085addb0f8b7` · self-key 0 · UTF-8 ≤3900 bytes · envelope only · absolute timestamp now+30s)
8. .done 발행

## 회장 보고 형식 (verbatim)

성공: **`PR_WATCHER_TERMINAL_CALLBACK_ROOT_CAUSE_PACKET_READY`**

끝
