웹 파일만 고친 PR에도 미디어 CI가 보이면 팀원은 자기 변경과 무관한 검사까지 해석해야 합니다. PokeClip은 웹, Spring 서비스, Go 미디어 코드를 한 저장소에서 관리하고 있어 이 문제가 실제 협업 피드백으로 나왔습니다. 경로 필터 한 줄을 추가하면 끝날 것 같지만, 브랜치 보호가 그 검사 이름을 필수로 기다리고 있었습니다.
저는 PR #163에서 미디어와 무관한 PR에는 워크플로를 표시하지 않되, 미디어가 바뀌면 기존 검사를 그대로 실행하는 쪽을 택했습니다. 이를 위해 필수 media-gate를 해제해야 했습니다. 검사 실패가 자동으로 병합을 막던 보호를 포기하는 선택까지 포함해 적용 순서와 롤백을 정했습니다. 워크플로 수정과 검증은 AI와 협업했습니다.
비싼 테스트는 이미 조건부였다
변경 전에도 미디어 테스트 자체가 모든 PR에서 실행된 것은 아닙니다. 내부 경로 필터가 비싼 검사를 걸렀습니다. 다만 media-gate를 main·develop의 필수 체크로 유지하려면 모든 PR에서 그 이름의 결과를 만들어야 했고, changes와 media-gate 잡이 계속 나타났습니다. 이 두 잡은 각각의 PR에서 대략 3~4초 실행되는 구조였습니다.
팀원이 불편해한 것은 단순 실행 시간보다 자신의 영역과 무관한 체크가 항상 보인다는 점이었습니다. 따라서 이미 조건부인 테스트를 더 빠르게 만드는 변경과는 문제의 축이 달랐습니다. 워크플로 자체를 미디어 변경에만 표시하려면 잡 내부 조건을 워크플로 수준 paths로 옮겨야 했습니다.
그런데 GitHub Actions의 경로 필터 문서는 필터로 생략한 워크플로의 필수 체크가 Pending으로 남을 수 있음을 설명합니다. 워크플로는 시작되지 않았는데 보호 규칙은 완료 결과를 기다리는 상태가 됩니다. 두 설정을 따로 수정하면 미디어와 무관한 PR까지 병합이 막힐 수 있습니다.
필수 체크를 유지할지, 표시를 없앨지 선택했다
기존처럼 모든 PR에 게이트 잡을 만드는 방식은 실패가 병합을 강제로 막게 할 수 있습니다. 반면 워크플로가 완전히 나타나지 않게 하려면 해당 이름을 필수 체크에서 빼야 했습니다. 당시 저는 후자를 선택했습니다. 이는 기술적으로 두 설정을 맞추는 일이면서 팀의 병합 규율을 바꾸는 일이기도 했습니다.
| 구분 | 변경 전 | 변경 후 |
|---|---|---|
| 실행 대상 판별 | 워크플로 안의 changes 잡 |
워크플로 수준 paths |
| 미디어와 무관한 PR | 경로 판별·게이트 잡 표시 | 미디어 워크플로 생략 |
| 실제 미디어 검사 | 변경이 있을 때 실행 | 변경이 있을 때 실행 |
필수 media-gate |
결과가 없거나 실패하면 병합 차단 | 필수 목록에서 제거 |
감지 경로는 media/**, 미디어 워크플로 파일, 루트 docker-compose.yml의 세 종류로 정했습니다. 미디어 소스만 보지 않은 이유는 검사 자체의 설정과 계약 테스트가 읽는 compose 변경도 영향을 줄 수 있기 때문입니다. changes, media-gate, 경로 필터 액션 의존은 제거하고 실제 check 잡 본문은 그대로 유지했습니다.
적용과 롤백의 순서는 서로 반대다
먼저 브랜치 보호에서 필수 media-gate를 제거하고, 다음으로 워크플로 PR을 병합하도록 정했습니다. 반대로 적용하면 새 워크플로는 더 이상 만들지 않는 체크를 보호 규칙이 기다립니다. 코드 변경만 리뷰해서는 보이지 않는 외부 설정 의존입니다.
롤백할 때도 단순히 필수 체크부터 다시 등록하면 안 됩니다. 워크플로를 되돌려 해당 브랜치를 대상으로 하는 PR에 media-gate 체크 런이 실제로 생성되는지 확인한 다음 필수 조건을 복원해야 합니다. main과 develop이 같은 워크플로 상태인지도 확인해야 합니다. 같은 규칙을 양쪽에 일괄 적용하는 것보다 각 브랜치의 현재 생성 결과가 기준이 됩니다.
| 검증한 항목 | 기록에 남은 결과 |
|---|---|
| 워크플로 파싱 | 잡은 check, 경로는 3종 |
| 실제 검사 본문 | 변경 전후 diff 0 |
| 해당 PR의 실행 | media-ci / check 1개 성공, changes·media-gate 없음 |
| 워크플로 분량 | 229행 → 173행 |
여기서 제가 중요하게 본 결과는 실제 check 잡 본문의 diff가 0이라는 점입니다. 실행 대상을 고르는 장치는 바꿨지만 race·정적 검사·패키지별 커버리지의 내용은 줄이지 않았습니다. 229행에서 173행으로 줄어든 부분은 경로 판별과 게이트 구성의 정리입니다. 해당 PR에서는 check 한 개의 성공을 확인했고, 변경은 2026년 9월 4일 한국 시각에 병합됐습니다. 이 확인만으로 이후 모든 PR의 실행 시간이나 비용 절감률까지 알 수는 없습니다.
검사 성공과 강제 병합 차단은 별개다
검사 내용을 그대로 두어도 병합 정책은 달라집니다. 이제 미디어 검사에 실패한 PR을 브랜치 보호가 자동으로 막아 주지 않습니다. 저는 이 빈자리를 팀이 확인해야 할 책임으로 명시하고, 병합 전 검사 결과 확인과 큰 PR의 분할을 운영 규칙으로 남겼습니다.
필수 품질 게이트를 유지해야 하는 팀이라면 항상 생성되는 게이트 잡이 더 적절할 수 있습니다. PokeClip에서 제가 선택한 것은 팀원이 보는 검사 목록을 담당 영역에 맞추는 대신, 결과 확인의 책임을 팀에 남기는 방식이었습니다. paths 한 줄을 넣기 전에 보호 규칙을 함께 읽어야 했던 이유도 여기 있습니다. 워크플로를 생략한다는 결정은 그 이름을 기다리던 병합 정책까지 바꿉니다.
이 글은 PokeClip·MediaMTX 기술 기록 모음의 일부입니다. 포트폴리오 전체본 35쪽에서 연결된 사례도 함께 볼 수 있습니다.