# task-485.1 보고서: 고스트 태스크 근본 수정

**팀**: dev1-team (헤르메스)
**작업 유형**: 버그 수정 (Critical 2건 + Important 2건 + Recommended 2건)
**일시**: 2026-03-12
**근거**: task-483.1 분석 보고서

---

## 수정 내역 (6건)

### 수정 1 [Critical]: _cleanup_task() 호출 시 timer_task_id 사용
- **파일**: `/home/jay/workspace/dispatch.py`
- **변경**: 6곳의 `_cleanup_task(task_id)` → `_cleanup_task(timer_task_id)` 변경
  - L421 (project_dir 미존재)
  - L468 (dynamic bot RuntimeError)
  - L474 (dynamic bot key None)
  - L487 (dev team key None)
  - L503 (subprocess TimeoutExpired)
  - L543 (cokacdir returncode != 0)
- **원인**: task_id에 점(.)이 없으면 timer에 `task-N.1`로 등록되는데, cleanup은 `task-N`으로 검색하여 정리 실패 → 고스트 태스크 생성

### 수정 2 [Critical]: bot_id 미할당 시 _cleanup_task() 추가
- **파일**: `/home/jay/workspace/dispatch.py` L481-483
- **변경**: `bot_id not in BOT_KEYS` 조건으로 return 하기 전에 `_cleanup_task(timer_task_id)` 호출 추가
- **원인**: task-timer start 이후 cleanup 없이 return하여 orphan running 엔트리 생성

### 수정 3 [Important]: _save_timers() 원자적 쓰기
- **파일**: `/home/jay/workspace/memory/task-timer.py` L83-105
- **변경**: `open("w")` 직접 쓰기 → `tempfile.NamedTemporaryFile` + `os.replace()` 원자적 교체
- **원인**: `open("w")`는 즉시 truncate하므로, 다른 프로세스가 빈 파일을 읽는 레이스 컨디션 발생

### 수정 4 [Important]: 락 메커니즘 통일
- **파일**: `/home/jay/workspace/dispatch.py`, `/home/jay/workspace/memory/task-timer.py`
- **변경**:
  - dispatch.py: 3곳의 `.task-id.lock` → `.task-timers.lock` (L128, L165, L319)
  - task-timer.py: `_load_timers()`와 `_save_timers()` 모두 외부 락 파일 `.task-timers.lock` 사용
- **효과**: dispatch.py와 task-timer.py가 동일한 락 파일로 상호 배제, readers-writer lock 정상 동작

### 수정 5 [Recommended]: teams/*/CLAUDE.md task-timer start 수동 호출 지시 제거
- **파일**:
  - `/home/jay/workspace/teams/dev1/CLAUDE.md` L17
  - `/home/jay/workspace/teams/dev2/CLAUDE.md` L17
  - `/home/jay/workspace/teams/dev3/CLAUDE.md` L19
- **변경**: "작업 시작 시 반드시 task-timer 시작" → "dispatch.py가 task-timer를 자동 처리하므로 task-timer.py를 직접 호출하지 마세요"
- **효과**: dispatch.py의 자동 start와 팀장 봇의 수동 start 이중 호출 문제 해소

### 수정 6 [Recommended]: generate_task_id() 하드코딩 기본값 방어
- **파일**: `/home/jay/workspace/dispatch.py` L172-197
- **변경**: 파일이 존재하는데 읽기 실패 시 기본값 `task-1.1` 사용 → `RuntimeError` 발생
- **효과**: 손상된 파일에 하드코딩 기본값을 덮어쓰는 시나리오 차단 (task-1.1 고스트 재생성 방지)

---

## 생성/수정 파일 목록

- `/home/jay/workspace/dispatch.py` (수정 1, 2, 4, 6)
- `/home/jay/workspace/memory/task-timer.py` (수정 3, 4)
- `/home/jay/workspace/teams/dev1/CLAUDE.md` (수정 5)
- `/home/jay/workspace/teams/dev2/CLAUDE.md` (수정 5)
- `/home/jay/workspace/teams/dev3/CLAUDE.md` (수정 5)
- `/home/jay/workspace/tests/test_dispatch.py` (기존 테스트 1건 수정)
- `/home/jay/workspace/tests/test_task_timer.py` (테스트 4건 추가)
- `/home/jay/workspace/teams/dev1/tests/test_dispatch_auto_timer.py` (테스트 5건 추가)

---

## 테스트 결과

- **전체**: 224개 PASSED (기존 215 + 신규 9)
- **pyright**: 0 errors, 0 warnings
- **회귀 없음**: 기존 테스트 전체 통과 (1건은 동작 변경에 맞게 수정)

### 신규 테스트 목록

- `TestCleanupUsesTimerTaskId::test_cleanup_uses_timer_task_id_not_raw_task_id` - 점 없는 task_id → timer_task_id로 cleanup
- `TestCleanupUsesTimerTaskId::test_cleanup_with_dotted_task_id_same` - 점 있는 task_id는 그대로
- `TestCleanupOnBotIdMissing::test_cleanup_called_when_bot_id_not_in_bot_keys` - bot_id 미할당 시 cleanup 호출
- `TestGenerateTaskIdCorrupted::test_corrupted_file_raises_runtime_error` - 파일 손상 시 RuntimeError
- `TestGenerateTaskIdCorrupted::test_nonexistent_file_returns_task_1_1` - 파일 없으면 정상 동작
- `TestAtomicSave::test_save_creates_valid_json` - 원자적 저장 후 유효한 JSON
- `TestAtomicSave::test_save_does_not_leave_tmp_files` - 임시 파일 잔여 없음
- `TestAtomicSave::test_concurrent_save_preserves_data` - 순차 저장 시 데이터 보존
- `TestAtomicSave::test_save_uses_lock_file` - .task-timers.lock 파일 생성 확인

### 수정된 기존 테스트

- `TestGenerateTaskIdErrorHandling::test_corrupted_json_falls_back_to_default` → `test_corrupted_json_raises_runtime_error`
  - 이유: Fix 6으로 동작이 변경됨 (기본값 사용 → RuntimeError 발생)

---

## 셀프 QC

- [x] 1. 영향 파일: dispatch.py, task-timer.py, CLAUDE.md 3개, 테스트 3개
- [x] 2. 엣지 케이스: task_id 점 유무, 파일 손상, 동시 쓰기 레이스 컨디션
- [x] 3. 작업 지시 일치: 6건 수정 전체 완료
- [x] 4. 에러 처리/보안: 원자적 쓰기, 손상 파일 방어, 통일 락
- [x] 5. 테스트 커버리지: 224 PASS, 회귀 0건

---

## 비고

- dashboard/ 파일 미수정 확인
- 기존 API/워크플로우 동작 변경 없음 (버그 수정만)
- DIRECT-WORKFLOW.md L40의 "중복 호출 불필요" 지시와 CLAUDE.md가 이제 일관성 있음
