콘텐츠로 건너뛰기
Codex

CI를 릴리스 게이트에서 뺀 이유

TECH NOTE · AUTOMATION OPS

CI 실패가 LFS와 줄바꿈 같은 체크아웃 문제였습니다. 릴리스 게이트를 로컬로 옮긴 판단과 그게 성립하는 조건을 적었습니다.

CI가 실패했는데 제품은 멀쩡했다

데스크톱 앱 릴리스를 CI에 걸어 뒀다. 태그를 밀면 macOS와 Windows에서 빌드하고, 그 결과를 배포 승인의 근거로 삼는 구조였다.

어느 날부터 계속 빨간불이었다. 원인을 보니 이랬다.

macos-latest:
  tauri::generate_context! panic
  failed to read icon .../icons/32x32.png: Invalid PNG signature

windows-latest:
  character-packs/hermes checksum mismatch
  persona/examples.jsonl 등 텍스트 파일이 Windows checkout에서
  CRLF 변환되어 checksum mismatch

둘 다 체크아웃 문제였다.

앞은 Git LFS를 안 받아서 PNG 자리에 포인터 텍스트가 있었던 것이고, 뒤는 Windows 러너가 텍스트 파일을 CRLF로 변환해서 체크섬이 안 맞은 것이다.

로컬에서는 둘 다 안 난다. 제품에는 아무 문제가 없었다.

고치는 방향이 마음에 안 들었다

수리 방향은 명확했다. 워크플로에 단계를 더 넣으면 됐다.

actions/checkout@v4 with lfs: true
Verify checked-out assets step 추가
  icons/32x32.png PNG signature 확인

여기에 .gitattributes로 줄바꿈을 고정하고, 러너별 예외를 넣고, 캐시를 손봐야 했다.

하나씩 보면 다 타당하다. 그런데 합쳐 놓고 보니 CI 환경을 로컬과 같게 만들려고 CI 전용 코드를 쌓고 있었다.

그 코드는 제품에 아무것도 기여하지 않는다. CI가 CI 자신의 문제를 해결하기 위해 존재하는 상태였다.

그래서 지웠다

워크플로 파일을 삭제하고 정책을 문서에 적었다.

GitHub 허용 범위:
  git remote 저장소
  fetch/pull/push

GitHub 금지 범위:
  GitHub Actions 실행/감시/재시도
  gh run list/view/watch/cancel
  CI artifact를 release gate로 사용

“CI를 안 쓴다”가 아니라 “릴리스 게이트로 쓰지 않는다” 가 정확한 표현이다. GitHub은 계속 저장소로 쓴다.

대신 검증을 로컬로 옮겼다.

cd apps/desktop-tauri
npm run release:preflight
npm run windows:preflight
cd ../..
cargo fmt --all
cargo test -p hermes_pet
cargo test -p pet-core
cd apps/desktop-tauri
npm ci
npm run build
npm run tauri:build:mac
npm run smoke:mac

Windows 결과물은 별도 Windows 기계에서 확인한다. CI가 아니라 실제 기계다.

왜 이게 성립했나

이 결정이 아무 데서나 통하지는 않는다. 성립한 조건이 있었다.

혼자 하는 프로젝트다. CI의 큰 값어치 중 하나는 여러 사람의 코드가 합쳐질 때 공통 기준을 주는 것이다. 합칠 사람이 없으면 그 값이 안 생긴다.

데스크톱 앱이다. 어차피 실기기 확인이 필요하다. 창이 투명하게 뜨는지, 항상 위에 있는지, 권한 요청이 제대로 나오는지는 CI가 못 본다. CI를 통과해도 사람이 한 번 더 봐야 한다면, CI는 중간 관문이지 게이트가 아니다.

비공개 저장소다. 외부 기여자에게 “이 검사를 통과해야 받는다”를 보여 줄 필요가 없다.

러너에 없는 것이 있다. 로컬 LLM, 실제 마이크, 서명 키. 이것들이 필요한 검증은 애초에 CI에서 못 한다.

반대로 CI를 지우면 안 되는 경우

솔직히 적자면 위 조건 중 하나만 무너져도 판단이 바뀐다.

조건 CI를 남겨야 하는 이유
기여자가 둘 이상 합치기 전 공통 기준이 필요하다
웹 서비스 실기기 확인이 없어 자동 검증이 곧 최종 검증
공개 저장소 외부 PR에 신뢰 가능한 신호가 필요하다
배포가 잦다 사람이 매번 돌리면 빠뜨린다

특히 마지막이 크다. 로컬 검증은 사람이 잊으면 안 돌아간다. CI는 잊어도 돌아간다. 그 차이를 무엇으로 메울지 답이 없으면 지우면 안 된다.

이 프로젝트는 배포가 드물고(0.1.0 하나), 검증 목록이 문서로 강제되고, 사전검증 스크립트가 낡은 증거를 막는다. 그 셋으로 메웠다.

지운 뒤에 생긴 일

좋아진 것과 나빠진 것이 둘 다 있다.

좋아진 것. 빨간불을 보고 “제품 문제인가 환경 문제인가”를 판단하는 시간이 사라졌다. 로컬에서 실패하면 무조건 제품 문제다.

나빠진 것. 검증을 안 돌리고 넘어갈 수 있게 됐다. 이건 실제 위험이고, 문서와 사전검증으로 막고 있지만 완전하지 않다.

그래서 로컬 검증 결과를 문서에 남기고, 그 문서가 낡으면 사전검증이 실패하게 만들었다. CI가 하던 “잊어도 돌아간다”의 일부를 다른 장치로 옮긴 셈이다.

판단 기준 하나

돌아보면 기준은 이거였다.

CI가 제품의 문제를 잡고 있나, 아니면 CI 자신의 문제를 잡고 있나.

LFS 설정, 줄바꿈 변환, 캐시 무효화, 러너 이미지 변경. 이런 걸 고치는 데 쓰는 시간이 늘어나면 신호가 뒤집힌다.

그 시점에 선택지는 셋이다. 제대로 투자해서 CI를 튼튼하게 만들거나, 검증 범위를 줄여 CI가 감당할 수 있는 것만 남기거나, 게이트를 옮기거나.

첫 번째가 대부분의 팀에게 맞는 답이다. 여기서는 프로젝트 조건이 세 번째를 허용했다.

정리

  • CI 실패가 LFS·줄바꿈 같은 체크아웃 문제였다. 제품에는 문제가 없었다
  • 수리 방향이 CI 환경을 로컬과 맞추는 CI 전용 코드를 쌓는 것이었다
  • “CI를 안 쓴다”가 아니라 “릴리스 게이트로 쓰지 않는다”. 저장소로는 계속 쓴다
  • 성립 조건: 혼자, 실기기 확인이 어차피 필요, 비공개, 러너에 없는 자원이 필요
  • 기여자가 둘 이상이거나 배포가 잦으면 판단이 반대다
  • CI가 주던 “잊어도 돌아간다” 를 다른 장치로 메워야 한다. 안 메우면 그냥 후퇴다
  • 기준: CI가 제품 문제를 잡는가, CI 자신의 문제를 잡는가

일반적인 권고가 아니다. 조건을 적어 두는 이유는 그 조건이 바뀌면 되돌리기 위해서다.

이 블로그 더 보기

이 블로그는 실제 프로젝트를 AI와 함께 굴리면서 남긴 기록입니다.

  • 새 글은 RSS로 받아볼 수 있습니다.
  • 시리즈 전체는 여기에 정리해 두었습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다