완성 그 이후 — 운영이라는 두 번째 산, 방치된 버그와 흔적 청소기 (6장)
"완성"이라 부른 지 한 달, 아무도 몰랐던 500 에러
7월 2일 유료화 전환까지 마치고, MVP v2는 문서상 "완성"이었다. 그런데 7월 31일, 무심코 시스템 상태를 확인하는 API(/api/config/status)를 호출했다가 500 에러를 받았다.
원인은 어이없을 만큼 단순했다. claude_available()이라는 함수를 호출하는 코드는 있는데, 그 함수를 import하는 줄이 빠져 있었다. NameError. 배포된 지 오래됐지만, 이 엔드포인트를 실제로 호출해본 사람이 아무도 없어서 아무도 몰랐다. 서비스가 "완성"됐다는 건 화면에 보이는 기능이 다 돌아간다는 뜻이지, 한 번도 안 눌러본 구석까지 다 멀쩡하다는 뜻은 아니었다.
로컬에서 그냥 켜는 것조차 순탄하지 않았다
같은 날, 로컬 개발 환경을 다시 세팅하다가 연달아 두 가지에 걸렸다.
첫 번째: pip install -r requirements.txt가 컴파일 에러로 실패했다. pydantic==2.9.0으로 정확히 버전을 고정해뒀는데, 이 버전은 최신 파이썬용 사전빌드 파일이 없어서 Rust 소스 코드를 직접 컴파일해야 했다. 그런데 컴파일에 필요한 MSVC 링커가 없었다. Visual Studio Build Tools를 새로 설치하는 대신, 버전 고정을 pydantic>=2.9.0,<3.0.0으로 완화하는 걸로 해결했다 — 운영 서버(Docker, 리눅스)는 애초에 이 문제가 없었다. 순수하게 로컬 개발 환경만의 문제였다.
두 번째, 그걸 고치고 나니 이번엔 서버가 아예 실행되지도 않았다.
UnicodeDecodeError: 'cp949' codec can't decode byte 0xe2
rate limiting 라이브러리(slowapi)가 내부적으로 .env 파일을 인코딩 지정 없이 다시 읽었는데, 한글 윈도우 로캘(cp949)로 읽으려다 UTF-8로 저장된 .env 파일에서 깨졌다. 이미 .env 값은 다른 방식(python-dotenv)으로 정상적으로 로드된 뒤였는데, 그 라이브러리가 굳이 한 번 더, 그것도 잘못된 방식으로 같은 파일을 읽으려던 것. 존재하지 않는 파일명을 지정해서 그 라이브러리의 자체 재읽기를 건너뛰게 만드는 걸로 해결했다.
3개월간 쌓인 코드에는, 처음 켤 때는 안 보이던 잔고장이 이렇게 숨어있었다.
흔적 청소 — Render라는 유령
호스팅을 Fly.io로 옮긴 지 한 달이 지났는데도, 코드 곳곳에 옛날 흔적이 남아있었다. render.yaml, Render 전용 빌드 스크립트, "Render 호환"이라고 적힌 주석들, 심지어 RSS 수집기의 User-Agent 문자열에도 이미 죽어버린 onrender.com 주소가 그대로 박혀있었다. 서비스를 해지한 지 오래됐지만, 코드는 그 사실을 몰랐다.
전부 걷어냈다. 실제 동작이 바뀌는 건 없었지만, 다음에 이 코드를 보는 사람(나 자신 포함)이 "Render 관련 설정이 왜 여기 있지?"라고 헷갈릴 여지를 없앴다.
도메인을 하나 더 붙였다
블로그(makewithai.kr)와 별개로, 서비스에 서브도메인(nugunalaw.makewithai.kr)을 새로 연결하기로 했다. 여기서 실제로 걸렸던 지점 — CORS는 여러 도메인을 콤마로 받아서 전부 허용하게 이미 고쳐뒀는데, 로그인 후 리다이렉트하는 코드는 그 값을 그대로 문자열에 꽂아 쓰고 있었다.
# 이렇게 쓰면 콤마가 그대로 URL에 들어가서 깨진 주소로 리다이렉트됨
return RedirectResponse(f"{FRONTEND_URL}?token={token}")
CORS는 "여러 도메인을 다 허용"해야 맞는 동작이지만, 로그인 리다이렉트는 "그 중 하나로만 보내야" 맞는 동작이다. 같은 환경변수를 쓰지만 용도가 다르다는 걸 놓쳤던 것. 콤마로 구분된 값 중 첫 번째만 리다이렉트 대상으로 쓰게 고쳐서 해결했다.
이 장에서 남는 것
"완성"은 상태가 아니라 계속 확인해야 하는 과정이었다. 죽은 API, 로컬 환경의 잔고장, 옛 호스팅의 유령, 새 설정과 옛 코드의 충돌 — 전부 "이미 다 됐다고 생각한 것"들 사이에서 나왔다. PM 출신이 여기서 배운 건 완성이 아니라 운영이 진짜 일이라는 것.
다음 장 예고
기업형 계약 분석 엔진 만들기 — 청킹, HWP 파서, 버전 비교, 플레이북.
이 글은 개인 개발 프로젝트의 기록이며, 실제 코드는 일부 축약·각색되었습니다.