# task-700.2 완료 보고서: Threads 업로드 자동 재시도 로직 추가

**S**: `ThreadsPublisher.publish_cardnews()`가 Threads API의 carousel 업로드를 1회만 시도하며, 간헐적 `error_subcode: 2207052` (미디어 URI 다운로드 실패) 발생 시 즉시 실패 처리된다.

**C**: 인프라(이미지 서버, Tailscale Funnel)는 정상이나, Threads API 측의 간헐적 오류로 업로드가 실패함. 재시도하면 성공하는 일시적 오류임에도 자동 복구가 없어 수동 재실행이 필요했다.

**Q**: carousel 업로드 실패 시 자동 재시도 로직을 추가하여 간헐적 오류를 자동 복구할 수 있는가?

**A**: `publish_cardnews()`의 carousel 업로드 구간(step 3)에 최대 3회, 30초 간격의 재시도 로직을 추가했다. pytest 39건 전체 통과 (기존 34 + 신규 5), pyright 에러 0건, black/isort 준수.

---

## 작업 내용

### 구현 사항
1. carousel 업로드 구간을 `for attempt in range(1, 4)` 루프로 래핑
2. 업로드 실패(예외 발생) 시 최대 3회 재시도
3. 재시도 간격: 30초 (`time.sleep(30)`)
4. 재시도 시 로그: `[RETRY] 업로드 재시도 {n}/3 (30초 후)...` (logger.warning)
5. 3회 모두 실패 시 기존과 동일한 에러 dict 반환
6. 성공 시 즉시 `break`로 루프 탈출

### 설계 결정
- `ThreadsClient` 인스턴스를 루프 밖에서 1회 생성, 루프 내에서 재사용 (매 시도마다 새 코루틴 생성)
- `import time`을 모듈 상단으로 이동 (테스트에서 `patch("publisher.threads_publisher.time.sleep")` 가능)
- `threads_post_id: str = ""` 초기화 추가 (pyright `reportPossiblyUnboundVariable` 해소)

## 수정 파일 목록
- `/home/jay/projects/ThreadAuto/publisher/threads_publisher.py` — 재시도 로직 추가 (lines 202-238)
- `/home/jay/projects/ThreadAuto/tests/test_publisher.py` — 재시도 테스트 5건 추가 (TestPublishCardnewsRetry 클래스)

## 미수정 파일 (제약사항 준수)
- `renderer/cardnews.py` — 미수정
- `prompts_v2.py` — 미수정
- `publish_cardnews()` 인터페이스(인자, 반환값) — 변경 없음

## 테스트 결과
- **pytest**: 39/39 통과 (100%)
  - 기존 34건: 전체 통과 (회귀 0건)
  - 신규 5건 (TestPublishCardnewsRetry): 전체 통과
    - `test_carousel_retry_succeeds_on_second_attempt` — 1회 실패 → 2회차 성공
    - `test_carousel_retry_succeeds_on_third_attempt` — 2회 실패 → 3회차 성공
    - `test_carousel_all_retries_exhausted` — 3회 모두 실패 → 에러 반환
    - `test_carousel_no_retry_on_success` — 1회차 성공 → 재시도 없음
    - `test_carousel_retry_logs_message` — `[RETRY]` 로그 메시지 확인
- **pyright**: 0 errors, 0 warnings, 0 informations
- **black/isort**: 포맷 준수 확인

## 발견 이슈 및 해결

### 자체 해결 (3건)
1. **`import time` 위치 문제** — 메서드 내 lazy import 시 테스트에서 `patch("publisher.threads_publisher.time.sleep")` 불가 → 모듈 상단으로 이동
   - 상세: `publisher/threads_publisher.py:7` `import time` 추가, 메서드 내 `import time` 제거
2. **pyright `reportPossiblyUnboundVariable`** — for 루프 내 `threads_post_id` 할당 후 루프 밖 참조 시 미바인딩 가능성 경고 → `threads_post_id: str = ""` 초기화 추가
3. **코루틴 재사용 불가** — `asyncio` 코루틴은 1회만 await 가능 → 매 재시도마다 `client.post_carousel()` 호출로 새 코루틴 생성

## 셀프 QC 체크리스트
- [x] 1. 다른 파일 영향 없음 (publisher 파일 1개만 수정)
- [x] 2. 엣지 케이스 검증: 1회 성공, 중간 성공, 3회 모두 실패
- [x] 3. 작업 지시와 정확히 일치 (MAX_RETRIES=3, RETRY_DELAY=30, 로그 포맷 일치)
- [x] 4. 에러 처리: 기존 에러 반환 형식 유지
- [x] 5. 테스트 5건으로 모든 경로 커버
- [x] 6. 발견 이슈 3건 모두 자체 해결
