버전 올리는 걸 자꾸 잊는다면? GitHub Actions로 배포 사고 막기

버전 올리는 걸 자꾸 잊는다면? GitHub Actions로 배포 사고 막기

2026년 2월 5일

크롬 익스텐션을 개발하고 Chrome Web Store에 제출하는 과정에서, 버전을 올리지 않아 빌드 산출물과 실제 코드가 어긋나는 문제를 겪었습니다.

처음에는 “다음부터 조심하면 되지”라고 생각했지만, 같은 실수가 반복되면서 사람의 주의력에 의존하는 프로세스 자체가 문제라는 걸 깨달았습니다.

이 글은 그 문제를 CI로 해결한 기록입니다.


1. 문제 상황

hanspoon은 크롬 익스텐션으로, 배포 흐름은 다음과 같다.

  1. develop 브랜치에서 기능 개발
  2. developmain으로 PR & 머지
  3. main에서 빌드 후 zip 파일 생성
  4. Chrome Web Store에 zip 제출

여기서 버전은 apps/extension/package.jsonversion 필드에 명시되어 있고, WXT가 빌드 시 이 값을 읽어 manifest.json에 반영한다.

문제는 버전을 올리는 주체가 사람이라는 점이었다.

실제로 겪었던 상황은 이렇다.

  • develop에서 여러 기능을 개발하고 main에 머지했는데, 버전을 올리지 않았다.
  • zip 파일을 만들어 제출했더니, 이전 제출과 버전이 동일해서 혼란이 생겼다.
  • 로컬에서는 코드가 바뀌어 있는데, 버전은 그대로 0.0.1이었다.
  • “이 zip이 최신인가? 예전 거인가?” 구분이 안 되는 상태가 되었다.

Chrome Web Store는 동일 버전으로 재제출이 가능하지만, 그렇기 때문에 오히려 버전이 달라지지 않아도 아무런 경고가 없다. 실수를 알아차릴 수 있는 지점이 어디에도 없었던 것이다.

2. 내가 처음 한 생각

처음에는 단순하게 생각했다.

“다음부터는 PR 올리기 전에 버전 올리는 걸 잊지 말자.”

즉, 사람이 더 조심하면 된다는 접근이었다.

실제로 이렇게 해봤다.

  • PR 템플릿에 “버전 올렸는지 확인” 체크리스트 항목을 머릿속으로 추가
  • 머지 전에 한 번 더 package.json을 열어보기

하지만 이 방식의 근본적인 문제는, 바쁠 때, 급할 때, 집중이 흐려질 때 정확히 같은 실수가 반복된다는 것이었다.

사람의 주의력에 의존하는 프로세스는 결국 실패한다. 이런 류의 문제는 semantic-releaseChangesets 같은 자동화 도구로 해결하는 팀도 많다. 커밋 메시지 컨벤션을 기반으로 버전을 자동 결정하고 릴리즈까지 자동화하는 방식이다.

하지만 이 프로젝트의 상황은 달랐다.

  • 1인 개발 프로젝트
  • 아직 릴리즈 주기가 정립되지 않은 초기 단계
  • 커밋 컨벤션이 엄격하게 지켜지고 있지 않음

이런 상황에서 semantic-release 같은 도구를 도입하면 설정 비용 대비 얻는 게 적다고 판단했다. 필요한 건 “자동으로 버전을 올려주는 것”이 아니라, “버전을 안 올렸을 때 알려주는 것”이었다.

3. 실제로 해 본 선택

관점을 바꿨다.

“사람이 잊는 건 막을 수 없다. 하지만 잊었을 때 알려줄 수는 있다.”

GitHub Actions로 PR 시점에 버전 변경 여부를 검증하는 CI를 추가했다.

name: Version Check

on:
  pull_request:
    branches: [main]
    paths:
      - "apps/extension/**"

jobs:
  check-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout PR branch
        uses: actions/checkout@v4

      - name: Fetch main branch
        run: git fetch origin main:refs/remotes/origin/main

      - name: Check extension version bump
        run: |
          if git show origin/main:apps/extension/package.json > /tmp/main-package.json 2>/dev/null; then
            CURRENT=$(jq -r '.version' /tmp/main-package.json)
          else
            echo "main 브랜치에 package.json이 없음 - 첫 릴리즈로 간주"
            CURRENT="0.0.0"
          fi

          NEW=$(jq -r '.version' apps/extension/package.json)

          echo "========================================"
          echo "현재 main 버전: $CURRENT"
          echo "PR 버전: $NEW"
          echo "========================================"

          if [ "$CURRENT" = "$NEW" ]; then
            echo ""
            echo "::error::버전이 변경되지 않았습니다!"
            echo ""
            echo "apps/extension/package.json의 version을 올려주세요."
            NEXT=$(echo "$CURRENT" | awk -F. '{print $1"."$2"."$3+1}')
            echo "예시: $CURRENT → $NEXT"
            exit 1
          fi

          echo ""
          echo "✅ 버전 변경 확인: $CURRENT → $NEW"

핵심 설계 의도는 다음과 같다.

트리거 조건을 좁힌다.

on:
  pull_request:
    branches: [main]
    paths:
      - "apps/extension/**"

main 브랜치로의 PR에서, 익스텐션 코드가 변경되었을 때만 실행된다. 문서만 수정하거나 다른 앱을 변경한 PR에서는 불필요하게 실행되지 않는다.

main 브랜치의 현재 버전과 비교한다.

CURRENT=$(jq -r '.version' /tmp/main-package.json)
NEW=$(jq -r '.version' apps/extension/package.json)

PR 브랜치의 버전이 main과 동일하면 실패한다. 단순히 “버전 필드가 있는가”가 아니라, “실제로 변경되었는가”를 검증한다.

실패 시 다음 행동을 안내한다.

NEXT=$(echo "$CURRENT" | awk -F. '{print $1"."$2"."$3+1}')
echo "예시: $CURRENT$NEXT"

단순히 “실패”만 알려주는 게 아니라, 다음 패치 버전을 제안해준다. CI가 경고 역할을 넘어서 가이드 역할까지 하도록 했다.

4. 결과

CI가 실제로 잡아낸 케이스

CI를 추가한 이후로, 버전 누락 문제는 발생하지 않았다.

실제로 이 CI가 추가된 직후의 커밋 히스토리를 보면

54dc8c5 chore: 버전 체크 CI 추가 (#9)
b25840a chore: 버전 업데이트 누락 및 자동 동기화 리팩토링 (#10)
2e3dc18 fix: 원격 스크립트 로딩 차단 (#11)
7597022 chore: 버전 0.0.3으로 업데이트

#10의 커밋 메시지에 “버전 업데이트 누락”이라는 단어가 보인다. 이것이 바로 CI가 잡아낸 케이스다. PR을 올렸을 때 CI가 실패하면서 버전을 올리지 않았음을 알게 되었고, 해당 PR에서 바로 수정할 수 있었다.

이 접근이 효과적이었던 이유를 정리하면

관점이전 (수동)이후 (CI 검증)
버전 누락 감지 시점Web Store 제출 후PR 단계에서 즉시
의존 대상사람의 기억력자동화된 검증
피드백없음에러 메시지 + 다음 버전 제안
비용0 (대신 실수 비용 높음)워크플로우 파일 1개 (48줄)

예상치 못한 문제: 머지 타이밍과의 경쟁 조건

하지만 CI를 운영하면서 예상치 못한 문제를 하나 더 만났다.

v0.0.3 릴리즈 PR을 머지한 직후, CI가 실패로 표시된 것이다.

현재 main 버전: 0.0.3
PR 버전: 0.0.3
❌ 버전이 변경되지 않았습니다!

분명 v0.0.2 → v0.0.3으로 올린 PR이었는데, 왜 같은 버전이라고 나올까?

원인은 CI가 비교하는 시점에 있었다.

기존 CI는 git fetch origin main으로 실시간 main 상태를 가져온다. 그런데 PR이 머지되면 main이 즉시 업데이트된다. CI 실행이 머지 직후에 걸리면, 이미 v0.0.3이 반영된 main과 PR의 v0.0.3을 비교하게 되어 “동일 버전”으로 판정하는 것이었다.

[머지 전] main: 0.0.2 vs PR: 0.0.3 → ✅ 통과
[머지 후] main: 0.0.3 vs PR: 0.0.3 → ❌ 실패 (경쟁 조건)

해결은 비교 대상을 바꾸는 것이었다.

# 기존: 실시간 main을 fetch
- name: Fetch main branch
  run: git fetch origin main:refs/remotes/origin/main

# 개선: PR 생성 시점의 base commit 사용
- name: Check extension version bump
  run: |
    BASE_SHA=${{ github.event.pull_request.base.sha }}
    CURRENT=$(git show ${BASE_SHA}:apps/extension/package.json | jq -r '.version')

github.event.pull_request.base.sha는 PR이 열릴 때 기록된 base 커밋이다. main이 머지로 업데이트되어도 이 값은 변하지 않기 때문에, 타이밍에 의한 오탐이 발생하지 않는다.

CI 실패해도 머지가 된다?

또 하나 발견한 문제가 있었다. CI가 실패로 표시되었는데도, 머지 버튼은 활성화된 상태였다.

CI를 추가했지만 GitHub의 Branch Protection Rule을 설정하지 않았던 것이다. CI는 경고만 해줄 뿐, 머지를 막지는 못하고 있었다.

# GitHub API로 Branch Protection Rule 설정
gh api repos/{owner}/{repo}/branches/main/protection \
  --method PUT \
  --input - <<'EOF'
{
  "required_status_checks": {
    "strict": false,
    "contexts": ["check-version"]
  },
  "enforce_admins": false,
  "required_pull_request_reviews": null,
  "restrictions": null
}
EOF

이 설정을 추가하면

  • check-version CI가 통과해야만 머지 가능
  • CI 실패 시 머지 버튼이 비활성화됨
  • enforce_admins: false로 설정하여, 긴급 상황에서는 admin이 우회 가능

CI가 “알려주는 역할”에서 “막는 역할”까지 하게 된 것이다.

5. 지금 다시 해본다면?

이 경험을 통해 느낀 점은, CI는 한 번 만들고 끝이 아니라 운영하면서 개선하는 것이라는 점이다.

처음에는 “버전이 바뀌었는가”만 확인하는 48줄짜리 워크플로우로 시작했다. 하지만 실제로 운영하면서 두 가지 문제를 더 만났고, 각각을 개선했다.

문제원인해결
버전 누락사람이 잊음CI로 PR 시점에 검증
머지 후 오탐실시간 main과 비교base.sha로 고정 시점 비교
CI 실패해도 머지됨Branch Protection 미설정Required Status Check 추가

다만 한 가지 남은 개선점이 있다.

현재는 “버전이 바뀌었는가”만 확인한다. 하지만 버전이 올라갔는지(예: 0.0.20.0.3)는 검증하지 않는다. 극단적으로, 0.0.30.0.1로 낮춰도 CI는 통과한다. 프로젝트가 커지면 semver 비교 로직을 추가하는 것도 고려할 수 있다.

또 하나 배운 점은, 자동화의 스펙트럼이다.

  • 완전 자동화 (semantic-release): 커밋 메시지 기반으로 버전 결정 + 릴리즈
  • 반자동화 (Changesets): 개발자가 변경 요약을 작성하면 버전 결정은 도구가
  • 검증만 (이 프로젝트): 버전 결정은 사람이, 누락 방지는 CI가

프로젝트의 규모와 팀 상황에 따라 적절한 단계를 선택하면 된다. 1인 프로젝트 초기 단계에서는 “검증만”으로도 충분했고, 도입 비용 대비 효과가 컸다.

결국 핵심은 이것이다.

사람이 실수하는 건 막을 수 없지만, 실수한 채로 배포되는 건 막을 수 있다.

참고