Files
variet-agent/.agent/references/known-issues.md

186 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Known Issues & Lessons Learned
> **이 파일은 SSOT(Single Source of Truth)입니다.**
> 디버깅이나 구현 전에 **반드시** 이 파일을 확인하세요.
> 세션 종료 시 새로 발견된 이슈를 이 파일에 추가합니다.
---
## 포맷
각 항목은 아래 형식을 따릅니다:
```markdown
### [날짜] [키워드] — 한줄 요약
- **증상**: 무엇이 잘못되었는가
- **원인**: 근본 원인
- **해결**: 올바른 해결 방법
- **주의**: 재발 방지를 위한 교훈
```
---
## 공통 이슈
### [2026-03-08] PowerShell curl — Invoke-WebRequest 충돌
- **증상**: `curl` 명령이 예상과 다른 응답 형식을 반환
- **원인**: PowerShell에서 `curl``Invoke-WebRequest`의 별칭
- **해결**: **`curl.exe`**를 명시적으로 사용
- **주의**: HTTP 관련 모든 명령에서 `curl.exe` 사용 필수
### [2026-03-08] PowerShell npm — 실행 정책 오류
- **증상**: `npm run` 명령이 `실행 정책` 관련 오류로 실패
- **원인**: PowerShell 스크립트 실행 정책이 제한적으로 설정됨
- **해결**: `cmd /c npm run dev` 형식으로 cmd를 통해 실행
- **주의**: npm 관련 명령은 항상 `cmd /c` 접두어 사용 권장
---
## 프로젝트별 이슈
> 아래에 프로젝트 특화 이슈를 추가하세요.
### [2026-03-08] nas_scanner docstring — Unicode escape SyntaxError
- **증상**: `from tools.nas_scanner import NasScanner``SyntaxError: (unicode error) 'unicodeescape' codec can't decode \N`
- **원인**: docstring 내 `\\NasData` 경로에서 `\N`이 Python Unicode named escape로 해석
- **해결**: docstring을 `r"""..."""` (raw string)으로 변경
- **주의**: Windows 경로(`\\`, `\N`, `\U` 등)가 포함된 docstring은 반드시 `r"""`로 작성
### [2026-03-08] title_matcher _kata_to_hira — 장음기호 깨짐
- **증상**: `フリーレン``ふり゜れん` (ー가 ゜로 변환)
- **원인**: 카타카나 범위 `0x30A0~0x30FF`에 기호 문자(`ー` U+30FC) 포함
- **해결**: 범위를 `0x30A1~0x30F6`으로 좁혀 실제 문자만 변환
- **주의**: 유니코드 범위 지정 시 기호/구두점 문자 포함 여부 확인 필수
### [2026-03-08] anime_pipeline — Nyaa 검색 0건 반환
- **증상**: 한자 포함 원제의 로마자 변환 결과(`葬送nofuriren`) + suffix 고정으로 Nyaa 검색 실패
- **원인**: 단일 검색 전략, suffix(ASW HEVC) 항상 부착
- **해결**: 6단계 fallback 전략 (romaji±suffix → 원제±suffix → 한글±suffix)
- **주의**: 외부 API 검색 시 반드시 다중 전략 + suffix 토글 구현
### [2026-03-15] _extract_episode — v2/S01E10 패턴 미인식 → 중복 다운로드
- **증상**: NAS에 ep9, 10이 있는데 재다운로드. ASW `- 10v2` 릴리스 에피소드 추출 실패
- **원인**: 정규식 `[-]\s*(\d{1,4})(?:\s|$|\.|\[)`이 v2 접미사 미처리. `S01E10`은 하이픈 패턴이 `S01``01`을 먼저 매칭
- **해결**: (1) SxxExx 패턴을 최우선 체크, (2) `(?:v\d)?` 추가로 version suffix 허용, (3) `\(` 추가로 SubsPlease 포맷 지원
- **주의**: 에피소드 추출 정규식 수정 시 반드시 v2/v3 릴리스 + SxxExx + 한글(N화) + false positive(29-sai) 테스트 포함
### [2026-03-15] _add_torrents — 릴리스 그룹 불일치 다운로드
- **증상**: NAS에 `[ASW] HEVC` 파일(~300MB)만 있는데 `CR WEB-DL DUAL`(1.4GB) 릴리스를 다운
- **원인**: 스코어링이 ASW에 +100을 주지만, ASW 릴리스가 없는 에피소드에서 아무 릴리스나 선택
- **해결**: NAS 기존 파일의 릴리스 그룹(`[ASW]`)을 감지하여 같은 그룹만 허용. 매칭 없으면 스킵
- **주의**: Nyaa 토렌트 제목에 영어+일본어 제목이 모두 포함되어 키워드 필터만으로는 불충분
### [2026-03-15] _download_subtitles — 기존 자막 덮어쓰기 위험
- **증상**: 이미 수동으로 배치한 자막 파일을 Anissia 자막으로 덮어쓸 수 있음
- **원인**: 기존 자막 파일 존재 여부를 확인하지 않고 전 에피소드 자막 다운로드 시도
- **해결**: NAS 폴더의 기존 자막 파일을 에피소드별로 스캔, 이미 있으면 스킵
- **주의**: 자막 처리 시 사용자 수동 입력 파일의 보존을 항상 고려
### [2026-03-15] Wiki.js GraphQL — update mutation에 tags 누락 시 에러
- **증상**: `update_page()` 호출 시 `Cannot read properties of undefined` 백엔드 에러
- **원인**: Wiki.js `update` mutation이 `tags` 파라미터 생략 시 내부적으로 undefined 처리하여 crash
- **해결**: `update_page()`에서 `tags`가 None이면 `get_page()`로 기존 tags를 먼저 조회하여 항상 전달
- **주의**: Wiki.js GraphQL mutation은 optional로 보이는 필드도 생략 시 에러 가능. 항상 모든 필드를 명시적으로 전달
### [2026-03-16] main.py StreamHandler — cp949 콘솔에서 한글/특수문자 UnicodeEncodeError
- **증상**: 봇 기동 시 `UnicodeEncodeError: 'cp949' codec can't encode character '\u2014'` 로 프로세스 비정상 종료
- **원인**: `logging.StreamHandler(sys.stdout)` 기본값이 시스템 인코딩(cp949) 사용. 로그 메시지의 em-dash(`—`) 등 유니코드 문자가 인코딩 불가
- **해결**: `io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")`로 StreamHandler를 UTF-8 고정
- **주의**: Windows 한글 환경에서 **모든 콘솔 출력**에 cp949 인코딩 문제 발생 가능. `subprocess.run``encoding="utf-8", errors="replace"` 명시 필수
### [2026-03-16] workspaces.json — 다른 PC 사용자 경로로 봇 기동 실패
- **증상**: `[WinError 267] 디렉터리 이름이 올바르지 않습니다` — Gemini agent 호출 시 cwd 오류
- **원인**: `workspaces.json`의 path가 다른 PC의 사용자 경로(`c:\Users\Certes\...`)로 하드코딩
- **해결**: 현재 머신의 사용자 경로(`c:\Users\Variet-Worker\...`)로 수정
- **주의**: 멀티 환경 배포 시 workspaces.json의 절대 경로가 환경별로 다를 수 있음. 상대 경로 또는 환경변수 사용 고려
### [2026-03-18] config.py — .env 값의 따옴표가 값에 포함됨
- **증상**: `.env``KEY="val,ue"` 형식으로 쓰면 비밀번호에 `"` 따옴표가 포함되어 인증 실패
- **원인**: `config.py`의 수동 `.env` 파서가 `value.strip()`만 하고 따옴표를 제거하지 않음
- **해결**: 양쪽 따옴표(`"..."` 또는 `'...'`) 감지 후 제거하는 로직 추가
- **주의**: `.env`에 특수문자(`,`, `&`, `#` 등) 포함 비밀번호는 반드시 따옴표로 감싸야 함
### [2026-03-18] Mailcow IMAP — 앱 비밀번호 LOGIN 커맨드 거부
- **증상**: `imaplib.login()` 호출 시 `AUTHENTICATIONFAILED` — 비밀번호 맞는데도 실패
- **원인**: Mailcow 앱 비밀번호는 IMAP `LOGIN` 커맨드를 거부하고 `PLAIN` 인증만 지원
- **해결**: `conn.authenticate("PLAIN", lambda x: ("\0" + user + "\0" + pw).encode())` 사용
- **주의**: 일반 계정 비밀번호는 `LOGIN`도 가능하지만, 앱 비밀번호는 반드시 `PLAIN` auth 필요
### [2026-03-18] WebDAV SEARCH — Nextcloud 501 Not Implemented
- **증상**: `nc_files.search()` 결과 0건, 로그에 `WebDAV SEARCH 실패: 501`
- **원인**: Nextcloud 인스턴스가 WebDAV `SEARCH` 메서드를 지원하지 않음
- **해결**: SEARCH 실패 시 PROPFIND depth=99 → 로컬 필터 폴백 (`nc_files.py`)
- **주의**: PROPFIND depth=99는 파일 수가 많으면 느릴 수 있음. 추후 OCS 파일 검색 API 검토
### [2026-03-18] anime_handler — action 분기 누락
- **증상**: 자연어 "자막 최신화" → "무엇을 도와드릴까요?" 표시
- **원인**: unified prompt가 `action: "download", title: ""` 반환 → `download and title` 조건 불충족 → else 분기
- **해결**: `download and not title` 분기 추가 (filter에 "sub" 포함 시 `batch_download(sub_only)`)
- **주의**: 새 action 추가 시 unified prompt와 handler 양쪽 매핑 반드시 확인
### [2026-03-18] discord_bot — 모듈 함수명 불일치
- **증상**: `cannot import name 'handle_anime_action'`
- **원인**: 존재하지 않는 함수명으로 import (실제: `handle_anime_message`)
- **해결**: import 수정 + 시그니처 확인 `(message, parsed)`
- **주의**: 핸들러 연결 시 반드시 실제 모듈의 함수명/시그니처 확인 후 코드 작성
### [2026-03-18] anime_handler — batch_download list 반환값 crash
- **증상**: title 없이 배치 다운로드 시 `AttributeError: 'list' object has no attribute 'message'`
- **원인**: `batch_download()``list[DownloadResult]`를 반환하지만, 렌더링 코드가 단일 `result.message` 접근
- **해결**: `isinstance(result, list)` 체크 추가 → list면 합산 Embed 렌더링
- **주의**: pipeline 메서드 반환 타입을 반드시 확인하고 handler에서 처리할 것
### [2026-03-18] anime_handler — NLU title에 범위 한정자 진입
- **증상**: "이번분기 애니 업데이트" → `title="이번분기"``download("이번분기")` → "검색 결과가 없습니다"
- **원인**: handler line 90이 `title` truthy면 무조건 단건 다운로드. title 유효성 검증 없음
- **해결**: `download()` resolve 실패 + episode 미지정 시 `batch_download()` fallback 추가
- **주의**: AI NLU 출력을 무비판적으로 신뢰하지 말 것. 코드에서 반드시 방어적 검증 필요
### [2026-03-19] anime_handler — batch mode "sub_only" 오판
- **증상**: "이번분기 애니 자막있는것까지 업데이트" → 영상 다운로드 안 됨
- **원인**: filter에 "sub" 있으면 `batch_download(mode="sub_only")` 호출 → `_execute_download` line 522가 영상 스킵
- **해결**: filter의 "sub"은 "자막도 포함"이지 "자막만"이 아님 → 모든 batch를 `mode="auto"`
- **주의**: `sub_only`는 명시적 `action="sub_only"`일 때만 사용. filter 값으로 mode 결정하지 말 것
### [2026-03-19] subtitle_downloader — Content-Disposition 후 존재 체크 누락
- **증상**: 이미 NAS에 있는 자막을 매번 다시 다운로드
- **원인**: Google Drive `sub.filename = "subtitle_{fileId}"` → 1차 존재 체크 통과 → 다운로드 → Content-Disposition에서 실제 파일명 → **존재 체크 없이 바로 write_bytes** → 기존 파일 덮어씀
- **해결**: Content-Disposition 파일명 결정 후, write 전에 2차 존재 체크 추가
- **주의**: 다운로드 전 체크만으로는 부족. **실제 파일명을 알게 된 시점에서 반드시 재확인**
### [2026-03-19] discord_bot — unified 분류 timeout 60초 부족
- **증상**: 커피갤러리 요약 등 복잡한 요청 시 "시간 초과 (60초)" 에러
- **원인**: `discord_bot.py:388` `timeout=60` 하드코딩
- **해결**: `timeout=120`으로 변경
- **주의**: 분류 프롬프트 복잡도에 따라 timeout 조정 필요. 기본값은 넉넉히
### [2026-03-19] discord_bot — chat 응답 raw JSON 출력
- **증상**: "일렉기타 갤러리 요약" → `"mode": "chat", "response": "..."` JSON 구조가 그대로 Discord에 출력
- **원인**: Gemini가 JSON 문자열 안에 raw newline 출력 → `json.loads(strict=True)` 파싱 실패 → fallback이 raw 텍스트 전체를 response에 넣음
- **해결**: `json.loads(strict=False)` + chat 응답 항상 Embed 사용 + raw JSON 감지 시 response 텍스트만 추출
- **주의**: `json.loads`는 항상 `strict=False` 사용. chat 응답은 항상 Embed
### [2026-03-19] subtitle — discovered_ep=None 시 에피소드 체크 전면 우회
- **증상**: `existing_sub_eps = {1..10}` 전부 있는데도 Google Drive에서 10개 ZIP 재다운로드
- **원인**: 블로그 포스트 제목에 에피소드 번호 없음 → `discovered_ep = None` → line 761/777 스킵 조건(`is not None`) 전부 False → 무조건 다운로드. `_extract_archive`도 기존 파일 존재 체크 없이 덮어씀
- **해결**: (1) `discovered_ep is None` + 모든 영상 에피소드에 자막 있으면 URL 페치 자체 스킵 (2) ZIP 해제 시 `out_path.exists()` 체크
- **주의**: 에피소드 기반 스킵은 `discovered_ep``sub.episode` 모두 None이면 완전 무력화. 반드시 **보충 체크** 필요
### [2026-03-21] wiki — singleByPath 미존재 페이지 에러
- **증상**: `upsert_page("new/path")` 호출 시 `RuntimeError: Wiki.js API 오류: This page does not exist`
- **원인**: Wiki.js `singleByPath` 쿼리는 미존재 페이지에 대해 null 대신 **GraphQL error**를 반환. 기존 `list_pages()` → 루프 패턴은 이 문제 없었음
- **해결**: `find_page()`에서 `RuntimeError` catch → `"does not exist"` 포함 시 `None` 반환. `wiki_debate.py``_query()`에서 동일 처리
- **주의**: Wiki.js GraphQL API는 not-found를 에러로 처리하는 경우가 있음. 새 쿼리 추가 시 반드시 미존재 케이스 테스트
### [2026-03-22] wiki — 빈 content 페이지 생성 거부
- **증상**: `debate_handler.start()` 초기화 시 `input-*`, `response-*` 페이지 생성 실패 — "Page content cannot be empty"
- **원인**: Wiki.js `pages.create` mutation이 content가 빈 문자열(`""`)이면 거부. 핸들러가 response/input 초기화 시 `""` 전달
- **해결**: 모든 빈 content를 placeholder 텍스트(`*(대기 중)*`)로 변경
- **주의**: Wiki.js에 페이지 생성/수정 시 **절대로 빈 content를 전달하지 말 것**. 항상 최소 placeholder 필요
### [2026-03-22] debate — Gemini slug에 따옴표 포함
- **증상**: 토론 Wiki 페이지 경로에 `"` 포함 → 브라우저에서 접근 불가, `singleByPath`로 조회 불가
- **원인**: Gemini에게 제목 요약 요청 시 `"신용리스크를 측정하는..."` 처럼 따옴표 포함 응답 → `WikiClient.slugify()`가 따옴표 미제거
- **해결**: (1) 프롬프트를 영문 2-3단어 키워드로 변경, (2) `.strip('"\'')` + `re.sub(r'[^\w\s-]', '')` 로 특수문자 제거, (3) slug 30자 제한
- **주의**: AI 응답을 slug/경로/파일명에 사용 시 **반드시 특수문자 sanitize** 필수. 따옴표, 괄호, 마크다운 기호 등 모두 제거