Skip to content

fix: 한국어 Windows 환경에서 설치·수집이 실패하는 문제 두 건 - #1

Open
sikkkkkkkkkk wants to merge 2 commits into
byungjunjang:masterfrom
sikkkkkkkkkk:fix/windows-korean-locale-setup
Open

sikkkkkkkkkk wants to merge 2 commits into
byungjunjang:masterfrom
sikkkkkkkkkk:fix/windows-korean-locale-setup

Conversation

@sikkkkkkkkkk

@sikkkkkkkkkk sikkkkkkkkkk commented Aug 31, 2026

Copy link
Copy Markdown

정정 안내 (2026-08-31). 최초 작성 시 2번 항목의 발생 조건을 잘못 적었습니다. "한글 경로면 발생" 이라고 썼는데 사실이 아닙니다 — UTF-8 모드일 때만 발생합니다. 아래 본문은 재현으로 확인한 내용으로 고쳐 썼습니다. 자세한 경위는 아래 코멘트에 남겼습니다.

무엇을 고치나

한국어 로케일 Windows 에서 겪은 두 건입니다. 1번은 기본 설치 절차에서 바로 터지는 문제이고, 2번은 특정 조건에서만 터지지만 곧 기본값이 될 조건입니다. 성격이 달라 나눠 적습니다.


1. requirements.txt — pip 이 cp949 로 읽어 설치가 통째로 실패 (기본 절차에서 발생)

README 가 안내하는 powershell -ExecutionPolicy Bypass -File scripts\setup.ps11/4 단계에서 즉시 실패합니다.

UnicodeDecodeError: 'cp949' codec can't decode byte 0xec in position 9: illegal multibyte sequence
decoding with 'cp949' codec failed

pip 은 requirements 파일을 locale.getpreferredencoding() 으로 디코딩합니다. 한국어 Windows 에서는 cp949 라서 파일 첫 줄의 한글 주석(# 범용 웹 크롤링 에이전트 …)에서 바로 죽습니다.

  • 경로와 무관합니다 — ASCII 경로에서도 재현됩니다. 한국어 로케일이면 누구나 맞습니다.
  • Python 3.12 번들 pip 24.0 기준입니다. 최신 pip 은 UTF-8 을 먼저 시도해서 넘어갑니다. 즉 python -m venv 로 갓 만든 venv 에서 발생합니다.

pip 은 첫 두 줄의 PEP 263 선언을 인식하므로 coding 선언 한 줄로 해결됩니다. 구버전 pip 에서도 동작하고, 수동 pip install -r requirements.txt 경로까지 함께 덮습니다.

재현 및 확인 (깨끗한 venv, pip 24.0, ASCII 경로)
# 수정 전
$ .venv\Scripts\python.exe -m pip install -r req.txt --dry-run --no-deps --no-index
UnicodeDecodeError: 'cp949' codec can't decode byte 0xec in position 9

# 수정 후 — 파싱 통과 (--no-index 라 받을 곳이 없다는 에러만 남음)
ERROR: Could not find a version that satisfies the requirement scrapling<0.5,>=0.4.8

2. CA 번들 — UTF-8 모드 + 비ASCII 경로에서 모든 HTTPS 요청 실패

curl_cffi.requests.exceptions.SSLError: Failed to perform,
curl: (77) error adding trust anchors from locations:
  CAfile: D:\...\001_웹크롤링 에이전트\...\.venv\Lib\site-packages\certifi\cacert.pem

발생 조건은 두 가지가 모두 맞을 때입니다.

  1. UTF-8 모드 (PYTHONUTF8=1 또는 -X utf8) — 이때 파이썬 파일시스템 인코딩이 UTF-8 이 되어 경로를 UTF-8 바이트로 넘깁니다
  2. CA 번들 경로에 비ASCII 문자 — Windows libcurl 은 그 경로를 ANSI(cp949) 로 엽니다

둘이 어긋나서 파일을 못 엽니다. 한쪽만으로는 발생하지 않습니다. 같은 한글 경로에서도 UTF-8 모드가 꺼져 있으면 정상 동작합니다(cp949 로 인코딩되어 ANSI 와 일치).

$ PYTHONUTF8=1  → curl: (77) error adding trust anchors ...
$ (설정 없음)    → STATUS 200

따라서 README 대로 설치한 사용자는 이 문제를 만나지 않습니다. 이 저장소는 PYTHONUTF8 을 어디서도 설정하지 않습니다.

그럼에도 고칠 값어치가 있다고 본 이유:

  • 한글 인코딩 문제를 겪는 사용자가 PYTHONUTF8=1 을 스스로 켜는 일이 흔합니다 (저도 1번 문제를 우회하려다 켰고, 그래서 이 버그를 만났습니다)
  • PEP 686 에 따라 Python 3.15 부터 UTF-8 모드가 기본이 될 예정입니다. 그 시점에는 조건 1이 자동 충족되어, 한글 경로에 둔 사용자 전원이 이 문제를 맞게 됩니다 — 이 저장소의 주 사용자층입니다

utils.ensure_ascii_ca_bundle() 이 번들을 ASCII 경로로 복사하고 CURL_CA_BUNDLE 을 그리로 향하게 합니다. utils import 시점에 적용되므로 생성되는 crawl_script.py 와 preflight 가 같은 조건으로 동작합니다. 경로가 이미 ASCII 면 아무것도 하지 않고, 사용자가 CURL_CA_BUNDLE 을 지정했다면 건드리지 않습니다.

확인: 수정본 + PYTHONUTF8=1 + 한글 경로 → preflight CORE PASS 13 / FAIL 0.

판단은 메인테이너 몫입니다. 지금 당장 아무도 겪지 않는 문제라 2번을 빼고 1번만 받는 선택도 합리적입니다. 그러시면 해당 커밋만 덜어내겠습니다.


2-1. preflight — CA 실패가 WARN 이라 '전체 통과' 로 덮이던 문제

위 상황이 실제로 벌어졌을 때 preflight 는 이렇게 보고했습니다:

[WARN] HTTP fetch (httpbin) — 네트워크 제한 가능: ...
       다음 명령: 오프라인/방화벽이면 정상. 연결 확인 후 재시도
CORE : PASS 12 / WARN 1 / FAIL 0
[OK] 전체 통과 — 수집 환경 준비 완료.

HTTPS 수집이 하나도 안 되는 상태인데 "전체 통과" 로 끝나고, 안내 문구가 오프라인을 가리켜 원인에서 멀어집니다. 이 경우만 FAIL 로 올렸습니다. 오프라인·방화벽 실패는 지금처럼 WARN 으로 남습니다.

이 변경은 2번을 빼더라도 단독으로 의미가 있다고 생각합니다.


변경 파일

파일 내용
requirements.txt PEP 263 coding 선언 + 지우지 말라는 주석
scripts/utils.py ensure_ascii_ca_bundle() 추가, import 시점 적용
scripts/preflight.py CA 실패만 FAIL 로 구분, 검증 전 ensure_ascii_ca_bundle() 호출
scripts/test_utils.py CA 번들 테스트 3건

테스트

  • CI job 재현python:3.11-slim 컨테이너, Linux fresh checkout: 418 passed, 두 sync 검사 OK, exit 0. master baseline 도 동일하게 통과 (아래 코멘트)
  • 로컬 Windows(3.12) — e2e 포함 431 passed (기존 428 + 신규 3)
  • 새 테스트는 fix 를 되돌리면 실패하는 것까지 확인했습니다

리뷰어가 볼 만한 지점

  • utils.py import 시점 부수효과. 이 모듈은 지금까지 import 부수효과가 없었습니다. 생성되는 crawl_script.py 가 첫 요청 전에 자동으로 거치게 하려면 이 자리가 확실하다고 봤는데, 명시적 호출을 선호하시면 바꾸겠습니다.
  • utils 를 import 하지 않는 수집 스크립트는 여전히 영향을 받습니다. venv 에 sitecustomize.py 를 심으면 완전히 덮이지만 더 침습적이라 택하지 않았습니다.
  • 새 테스트가 tmp_path 를 쓰지 않는 이유. pytest.ini--basetemp=.tmp/pytest 때문에 tmp_path 가 저장소 안에 생기는데, 저장소가 한글 폴더면 그 경로가 비ASCII 라 "경로의 ASCII 여부" 를 검증할 수 없습니다. 그래서 ASCII 상위 경로를 따로 찾습니다.
  • 별건 하나sync_codex_mirror.pybuild_mirror()write_text() 로 쓰는데 개행이 플랫폼 기본값입니다. Windows 에서 실행해 커밋하면 미러가 CRLF 로 들어가 CI 가 깨질 수 있습니다. newline="\n" 으로 막을 수 있습니다. 범위 밖이라 건드리지 않았습니다.

🤖 Generated with Claude Code

sikkkkkkkkkk and others added 2 commits August 31, 2026 23:39
한국어 로케일 Windows 에서 갓 clone 한 저장소가 설치 단계에서 멈추거나,
설치가 '전체 통과' 로 보고된 뒤 수집이 전부 실패하는 문제를 고친다.
둘 다 이 도구의 주 사용자층(한국어 Windows 비개발자)이 정면으로 맞는 조건이다.

1) requirements.txt — pip 이 파일을 로케일 인코딩(cp949)으로 읽어 실패

   pip 은 requirements 파일을 locale.getpreferredencoding() 으로 디코딩한다.
   한국어 Windows 에서는 cp949 라서 파일 첫 줄의 한글 주석에서 바로 죽는다:

     UnicodeDecodeError: 'cp949' codec can't decode byte 0xec in position 9

   setup.ps1 의 1/4 단계가 통째로 실패한다. 경로와는 무관하며(ASCII 경로에서도 재현)
   한국어 로케일이면 누구나 맞는다.

   pip 은 첫 두 줄의 PEP 263 선언을 인식하므로 coding 선언 한 줄로 해결된다.
   pip 24.0(3.12 번들본)에서 재현 후 수정 확인했다.

2) CA 번들 — 경로에 한글이 있으면 모든 HTTPS 요청이 실패

   Windows 의 libcurl 은 CA 파일 경로를 ANSI 로 연다. 저장소를 한글 폴더에 두거나
   사용자명이 한글이면 certifi 번들을 못 읽고 모든 요청이 죽는다:

     curl: (77) error adding trust anchors from locations: CAfile: ...

   utils.ensure_ascii_ca_bundle() 이 번들을 ASCII 경로로 복사하고 CURL_CA_BUNDLE
   을 그리로 향하게 한다. utils import 시점에 적용되므로 생성되는 crawl_script.py
   와 preflight 가 같은 조건으로 동작한다. 사용자가 이미 CURL_CA_BUNDLE 을
   지정했으면 건드리지 않는다.

   preflight 의 HTTP fetch 검증에서 이 경우만 WARN 이 아니라 FAIL 로 올린다.
   기존에는 '네트워크 제한 가능' WARN 이라 CORE 가 전부 PASS 로 요약되고
   '[OK] 전체 통과' 로 끝났다 — 실제로는 수집이 하나도 안 되는 상태였다.
   오프라인·방화벽으로 인한 실패는 지금처럼 WARN 으로 남는다.

테스트: 431 passed (기존 428 + CA 번들 3건). 새 테스트는 저장소 위치와 무관하게
동작한다 — conftest 가 tmp_path 를 저장소 안으로 고정해서 ASCII 상위 경로를 따로 찾는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ensure_ascii_ca_bundle() 호출을 fetch 의 try 블록 밖으로 뺀다.

같은 블록 안에 두면 `import utils` 가 실패했을 때 바깥 except 가 그것을 잡아
"네트워크 제한 가능: No module named 'utils'" 로 보고한다. 원인은 import 인데
안내는 오프라인·방화벽을 가리키게 되어 진단을 엉뚱한 곳으로 보낸다.

import 이 실패해도 fetch 자체는 그대로 시도한다 — CA 처리는 보조이고,
검증의 본체는 fetch 다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sikkkkkkkkkk

Copy link
Copy Markdown
Author

CI 검증 (fork PR 이라 워크플로 승인 전이어서 로컬에서 동일 조건으로 돌렸습니다)

.github/workflows/checks.yml 의 job 을 그대로 재현했습니다 — python:3.11-slim 컨테이너에서 Linux fresh checkout(actions/checkout 과 같은 LF 체크아웃) 후 세 단계 모두 실행했습니다.

=== python ===        Python 3.11.14
=== Install ===       pip install -r requirements.txt          OK
=== Unit tests ===    418 passed, 13 deselected                OK
=== Generated blocks are in sync ===
                      sync_domain_list.py --check    [OK] 도메인 목록 최신 — 14개
                      sync_codex_mirror.py --check   OK
=== CI OK ===         exit 0

같은 조건에서 master 도 함께 돌려 baseline 을 확인했습니다 (동일하게 exit 0). 이 브랜치가 CI 를 새로 깨는 부분은 없습니다.

Windows 로컬(3.12)에서는 e2e 포함 431 passed 입니다.

참고 — 처음에 sync_codex_mirror --check 가 STALE 로 나왔는데 재현 환경 문제였습니다

Windows 작업트리를 컨테이너에 그대로 마운트해서 돌렸더니 _GENERATED.md 가 STALE 로 나왔습니다. 원인은 core.autocrlf=true 로 체크아웃된 작업트리 파일이 CRLF 인데, 스크립트가 Linux 에서 재생성하면 LF 라서 바이트 비교가 어긋난 것이었습니다. git blob 자체는 LF 이고(git show HEAD:.codex/skills/_GENERATED.md 확인) 실제 CI 는 Linux 에서 체크아웃하므로 해당되지 않습니다. 위 결과는 컨테이너 안에서 새로 clone 해 이 조건을 제거하고 받은 것입니다.

이 PR 과는 무관하지만, build_mirror()write_text() 가 플랫폼 기본 개행을 쓰기 때문에 Windows 에서 sync_codex_mirror.py 를 실행해 커밋하면 미러가 CRLF 로 들어가 CI 가 깨질 수 있습니다. newline="\n" 을 주면 방지됩니다. 별건이라 이 PR 에서는 건드리지 않았습니다.


추가 커밋 하나 더 올렸습니다 (cec992e) — ensure_ascii_ca_bundle() 호출을 fetch 의 try 밖으로 뺐습니다. 안에 두면 import utils 실패가 바깥 except 에 잡혀 "네트워크 제한 가능: No module named 'utils'" 로 보고돼, 원인은 import 인데 안내는 오프라인을 가리키게 됩니다.

@sikkkkkkkkkk

Copy link
Copy Markdown
Author

정정 — 2번 항목의 발생 조건을 잘못 적었습니다

본문을 수정했습니다. 제출 당시 "한글 경로에 clone 하면 모든 HTTPS 요청이 죽는다" 고 썼는데 사실이 아닙니다.

정확한 조건은 PYTHONUTF8=1(또는 -X utf8) + 비ASCII 경로가 동시에 맞을 때입니다. 같은 한글 경로에서도 UTF-8 모드가 꺼져 있으면 정상 동작합니다.

# 같은 저장소, 같은 한글 경로, 수정 없는 master
$ PYTHONUTF8=1 → curl: (77) error adding trust anchors ...
$ (설정 없음)   → STATUS 200

경위. 1번(pip cp949) 문제를 우회하려고 제 셋업 명령에 PYTHONUTF8=1 을 넣었는데, 그게 2번의 발생 조건이었습니다. 즉 제가 만든 조건에서 나온 증상을 경로 탓으로 진단했습니다. 처음 몇 번의 실행이 전부 그 플래그를 달고 있어서 차이를 못 봤습니다.

FAIL 분기 검증도 부실했습니다. 없는 파일(C:\nope\한글\missing.pem)을 가리켜 실패시켰는데, 그건 ASCII 경로여도 똑같이 실패하므로 "한글이 원인" 의 증거가 못 됩니다. 에러 감지 분기가 동작한다는 것만 보였을 뿐입니다.

PR 을 유지한 이유. 조건을 알고 나서도 고칠 값어치는 있다고 판단했습니다. 사용자가 인코딩 문제로 PYTHONUTF8=1 을 스스로 켜는 일이 흔하고(제 경우가 그랬습니다), PEP 686 으로 Python 3.15 부터 UTF-8 모드가 기본이 되면 조건 1이 자동 충족되어 한글 경로 사용자 전원이 맞게 됩니다.

다만 지금 당장 아무도 겪지 않는 문제인 것은 맞습니다. 2번을 빼고 1번만 받으시는 편이 낫다고 보시면 해당 커밋을 덜어내겠습니다. 판단에 필요한 정보를 처음에 틀리게 드려서 죄송합니다.

1번(pip cp949) 은 영향받지 않습니다. PYTHONUTF8 없이 깨끗한 venv 의 pip 24.0 에서 독립적으로 재현·확인했고, README 의 기본 설치 절차에서 그대로 발생합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant