# task-1973 보고서: 인슈로 anu_provider subprocess→asyncio 전환

**작성자**: 헤르메스 (개발1팀장)
**작성일**: 2026-04-20
**검증 레벨**: critical
**프로젝트**: insuro

---

## SCQA

**S**: anu_provider.py에서 Claude CLI 호출 시 `subprocess.run`을 사용 중이며, task-1969에서 `anyio.to_thread.run_sync`로 감싼 중간 전환 상태. `subprocess` 모듈 직접 의존이 남아 있음.

**C**: `subprocess.run`은 동기 블로킹 호출이라 스레드풀 오버헤드가 발생하고, `asyncio.TimeoutError` 발생 시 프로세스가 자동 종료되지 않아 리소스 누수 위험.

**Q**: subprocess 모듈 의존을 완전 제거하고 native async 프로세스 실행으로 전환할 수 있는가?

**A**: `asyncio.create_subprocess_exec`로 전환 완료. `import subprocess` 제거, `anyio.to_thread` 제거. 타임아웃 시 `process.kill() + await process.wait()` 추가로 리소스 누수 방지. pytest 139건 PASS, tsc 0 errors, 서버 정상 기동 확인.

---

## 수정 내역

### anu_provider.py 전환 (불칸)

- `import subprocess` → `import asyncio` 교체
- `from anyio import to_thread` 제거
- `_run_claude_cli()` 동기 함수 → `_run_claude_cli_async()` async 함수
- `subprocess.run(["claude", ...])` → `asyncio.create_subprocess_exec("claude", ...)`
- `subprocess.TimeoutExpired` → `asyncio.TimeoutError` + `process.kill()` + `await process.wait()`
- `result.stdout` → `stdout.decode(errors='replace').strip()`
- `generate()` 내 `to_thread.run_sync(lambda: ...)` → `return await _run_claude_cli_async(...)`

### 테스트 mock 전환 (아르고스)

- test_anu_provider.py: 4개 테스트 — `patch("anu_provider.subprocess.run")` → `patch("anu_provider.asyncio.create_subprocess_exec")`
- test_main.py: 1개 테스트 — 동일 패턴 전환
- `MagicMock(returncode, stdout, stderr)` → `AsyncMock(communicate, returncode)`
- conftest.py의 `mock_subprocess_claude` fixture 영향 없음 (ai_parser 전용)

### Gemini PR 리뷰 대응 (헤르메스)

- **HIGH 수용**: 타임아웃 시 `process.kill()` + `await process.wait()` 추가 (리소스 누수 방지)
- **MEDIUM 수용 (2건)**: `stderr.decode()` / `stdout.decode()` → `errors='replace'` 추가 (인코딩 안전성)

---

## 발견 이슈 및 해결

### 자체 해결 (1건)
1. **타임아웃 시 프로세스 미종료** — `process.kill() + await process.wait()` 추가
   - 상세: Gemini HIGH 지적. asyncio.wait_for 타임아웃 시 자식 프로세스가 좀비 상태로 남는 문제

### 범위 외 미해결 (1건)
1. **ai_parser.py의 subprocess 사용** — 범위 외 사유: 별도 파일, 별도 리팩토링 작업 필요

---

## 테스트 결과

- **pytest**: 139 passed, 0 failed (2.81s)
- **tsc**: 0 errors
- **회귀 테스트**: 작업 전 139 PASS → 작업 후 139 PASS (회귀 0건)

## L1 스모크테스트 결과

- 서버 재시작: 성공 (uvicorn main:app --port 8001)
- API 응답 확인: /api/status → {"status":"ok"} (200)
- 스크린샷: 해당없음 (백엔드 리팩토링)

---

## 머지 판단

- **머지 필요**: Yes → PR #3 MERGED
- **브랜치**: task/task-1973-dev1
- **머지 의견**: Gemini HIGH 1건 수정 완료, MEDIUM 2건 수용. pytest 139 PASS, 서버 정상 기동.

---

## 검증 시그니처

- `grep "subprocess.run\|subprocess.call\|subprocess.check" server/anu_provider.py` → **0건** ✅
- `grep "import subprocess" server/anu_provider.py` → **0건** ✅
- pytest → **139 passed** ✅
- tsc → **0 errors** ✅
- uvicorn → **/api/status ok** ✅

---

## 모델 사용 기록

- 불칸 / 백엔드 / sonnet / anu_provider.py asyncio 전환
- 아르고스 / 테스터 / sonnet / 테스트 mock 패턴 전환
- 헤르메스 / 팀장 / opus / 설계, Gemini HIGH 수정, 통합 검증, PR 관리

---

## 산출물 파일 목록

- /home/jay/projects/InsuRo/server/anu_provider.py
- /home/jay/projects/InsuRo/server/tests/test_anu_provider.py
- /home/jay/projects/InsuRo/server/tests/test_main.py

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

