Skip to content

ci: base 와 충돌한 PR 에 conflict 라벨을 붙인다 (closes 1028) [base: develop] - #1036

Merged
1hyok merged 2 commits into
developfrom
ci/1028-conflict-label
Aug 24, 2026
Merged

1hyok merged 2 commits into
developfrom
ci/1028-conflict-label

Conversation

@1hyok

@1hyok 1hyok commented Aug 24, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1028

무엇을 고치나

PR 이 base 와 충돌하면 그 PR 의 pull_request 워크플로가 트리거조차 되지 않는다. GitHub 이 base 와 head 를 합친 refs/pull/<N>/merge 를 만들지 못하기 때문이다. 체크가 실패하는 게 아니라 아예 생성되지 않아 no checks reported 로 비고, 저자도 리뷰어도 「아직 안 끝났나」로 읽어 최신 커밋이 미검증인 채 남는다.

머지 자체는 mergeable_state=dirty 와 required check(guard)의 pending 이 이중으로 막는다. 그래서 이 PR 이 고치는 것은 게이트가 아니라 가시성이다.

어떻게

열린 PR 전수의 mergeable 을 GraphQL 로 훑어, 충돌이면 conflict 라벨을 붙이고 해소되면 뗀다.

설계에서 의도한 결정 넷:

  • pull_request 트리거를 쓰지 않는다. 그것이 바로 「트리거되지 않는」 문제의 원인이라, 이 워크플로가 같은 함정에 빠지면 안 된다.
  • UNKNOWN 은 충돌로 단정하지 않는다. GitHub 이 mergeable 을 비동기로 계산하고 첫 응답은 대개 UNKNOWN 이다. 2·4초 간격으로 3회까지 재조회하고, 그래도 미확정이면 라벨을 건드리지 않는다 — 멀쩡한 PR 에 라벨이 붙는 오탐이 라벨 없음보다 나쁘다. 라벨이 이미 붙은 UNKNOWN 도 성급히 떼지 않는다.
  • 안내 코멘트는 1회만. 라벨 유무로 1차, 코멘트 마커(<!-- conflict-label -->)로 2차 확인한다. 사람이 라벨을 손으로 뗐다 다시 붙어도 안내가 중복되지 않는다.
  • 개별 PR 실패를 격리한다. 하나가 실패해도 나머지를 처리한 뒤 마지막에 모아서 실패시킨다.

conflict 라벨은 없으면 자동으로 만든다. 스택 PR(base 가 feat/*)도 base 가 develop 이 아닐 뿐 메커니즘이 같아 같은 규칙으로 본다.

단계적으로 켠다 — 이 PR 은 수동 실행 전용

도입 시점에 이미 충돌 PR 이 10건이라, push·schedule 을 켠 채 머지하면 첫 실행에서 라벨과 안내 코멘트가 한꺼번에 쏟아진다. 그래서 이 PR 은 workflow_dispatch 만 열어 두고, dry_run 기본값을 true 로 뒀다.

순서는 이렇게 본다.

  1. 이 PR 머지 — workflow_dispatch 는 워크플로 파일이 기본 브랜치에 있어야 실행할 수 있어서, 머지가 선행돼야 한다.
  2. gh workflow run conflict-label.yml -f dry_run=true 로 실제 러너 환경에서 판정 확인 — 로컬 dry-run 과 결과가 같은지, secrets.GITHUB_TOKEN 권한으로 조회가 되는지를 본다.
  3. 결과가 맞으면 dry_run=false 로 한 번 돌려 라벨을 붙이고, 그다음 push·schedule 트리거를 여는 후속 PR 을 낸다.

되살릴 트리거는 워크플로 파일 주석에 그대로 남겨 뒀다.

검증

항목 결과
신규 단위 테스트 18개 통과 — 계획 수립·재조회·쓰기 경로·실패 격리·dry-run
전체 스크립트 테스트 node --test .github/scripts/*.test.mjs → 80개 통과
Repository Quality actionlint + shellcheck 17 workflows 통과 (exit 0)

실서비스 레포 dry-run — 이 스크립트를 로컬에서 DRY_RUN=true 로 돌려 충돌 10건을 검출했고, 전부 mergeable=false · mergeable_state=dirty 로 대조 확인했다.

- 라벨 부착: 10건 — #771, #886, #925, #947, #954, #958, #961, #967, #988, #989
- 라벨 제거: 0건
- 판정 보류(UNKNOWN): 0건

수동으로 셌을 때는 7건이었는데 3건(#771·#967·#989)이 더 나왔다. 그때 UNKNOWN 이라 빠졌던 것들이고, 재조회 로직이 실제로 값을 한다는 증거다.

시각 검증 — 해당 없음. CI 스크립트와 워크플로만 바뀌어 Compose 본문·디자인 토큰·리소스 변경이 0건이다. screenshot baseline 사이클과 에뮬레이터 실측 모두 대상이 없다. Kotlin 모듈 변경이 없어 compileDebugKotlin 도 해당 없고, 대신 위 node 테스트와 actionlint 로 갈음했다.

리뷰어에게 알려둘 점

1hyok added 2 commits August 24, 2026 19:19
충돌 PR 은 pull_request 워크플로가 트리거조차 되지 않는다 — GitHub 이 base 와 head 를
합친 refs/pull/<N>/merge 를 만들지 못하기 때문이다. 체크가 실패하는 게 아니라 아예
생성되지 않아 no checks reported 로 비고, 저자도 리뷰어도 「아직 안 끝났나」로 읽어
최신 커밋이 미검증인 채 남는다.

머지 자체는 mergeable_state=dirty 와 required check 의 pending 이 막으므로, 고칠 것은
게이트가 아니라 가시성이다. develop push 직후와 매시 주기로 열린 PR 의 mergeable 을
훑어 conflict 라벨을 붙이고 해소되면 뗀다.

- pull_request 트리거를 쓰지 않는다. 그것이 바로 «트리거되지 않는» 문제의 원인이다
- UNKNOWN 은 충돌로 단정하지 않는다. 3회 재조회 후에도 UNKNOWN 이면 보류한다 —
  멀쩡한 PR 에 라벨이 붙는 오탐이 라벨 없음보다 나쁘다
- 안내 코멘트는 라벨 유무와 마커로 이중 확인해 1회만 남긴다
- 개별 PR 실패를 격리해 나머지를 처리한 뒤 한 번에 드러낸다

검증: 신규 단위 테스트 18개, 전체 스크립트 테스트 80개 통과. actionlint·shellcheck
(Repository Quality) 17 workflows 통과. 실서비스 레포 dry-run 으로 충돌 10건을
정확히 검출했고 전부 mergeable=false 로 대조 확인했다.
도입 시점에 이미 충돌 PR 이 10건이라, push·schedule 을 켠 채 머지하면 첫 실행에서
라벨과 안내 코멘트가 한꺼번에 쏟아진다. 먼저 dry_run 으로 실제 러너 환경의 판정을
확인하고, 그 결과를 보고 두 트리거를 여는 후속 PR 을 낸다.

되살릴 트리거는 주석으로 파일에 남긴다. dry_run 기본값은 true 로 둔다 — 자동 트리거가
없는 동안에는 안전한 쪽이 기본이어야 한다.
@1hyok

1hyok commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

#847 작업(PR #1039)과 겹치는 지점이 하나 있어 알려 둡니다 — 파일 충돌은 없습니다(merge-tree 실측: 충돌 0건). 의미 쪽입니다.

#1039 가 이 저장소의 외부 uses 를 전부 40자리 commit SHA 로 고정하고, supply-chain-policy.test.mjs 정책으로 그것을 강제합니다. 그런데 이 PR 이 새로 만드는 conflict-label.yml 은 부동 참조를 씁니다.

.github/workflows/conflict-label.yml:44  uses: actions/checkout@v7

둘 다 develop 에 들어가면 나중에 들어간 쪽 이후로 Unit Test 가 빨개집니다 (node --test .github/scripts/*.test.mjs 안의 every third-party action is pinned to an immutable commit SHA).

고치는 것은 한 줄입니다. 이 PR 이 먼저 머지될 것 같으면 여기서 미리 바꿔 두셔도 됩니다.

uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

(# v7.0.1 주석도 정책이 요구합니다 — SHA 만 있으면 사람이 못 읽고 Dependabot 갱신 대조도 안 되기 때문입니다.)

#1039 가 먼저 들어가면 제가 이쪽을 맞추겠습니다. 순서만 알려 주세요.

한 가지 더 — conflict-label.yml 은 workflow_dispatch 전용이라 #848(PR #1038)이 만드는 ci-gate 집계 대상이 아닙니다. 그쪽은 PR 검증 4종(lint·unit-test·screenshot·repository-quality)만 묶으므로 이 PR 과 부딪히지 않습니다.

@1hyok
1hyok merged commit a6d8ecf into develop Aug 24, 2026
15 checks passed
@1hyok
1hyok deleted the ci/1028-conflict-label branch August 24, 2026 10:52
@github-actions

Copy link
Copy Markdown
Contributor

🚀 QA 배포 권장

QA 포인트

이 판단은 배포를 자동 실행하지 않습니다. deploy일 때 Release Distribution을 수동 실행하세요.

1hyok added a commit that referenced this pull request Aug 24, 2026
#1036 이 이 PR 보다 먼저 develop 에 들어오며 미고정 actions/checkout@v7 을
남겼다. PR #1036 에 미리 알려 두었고 순서가 그렇게 났으므로 이쪽에서 맞춘다.
다른 워크플로와 같은 리비전(v7.0.1)이다.
@1hyok

1hyok commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

머지 순서가 이쪽이 먼저 나서, 예고한 대로 #1039 에서 맞췄습니다 — 이 PR 에서 하실 일은 없습니다.

.github/workflows/conflict-label.yml:44
- uses: actions/checkout@v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

#1039 기준 미고정 외부 uses 0건, node --test .github/scripts/*.test.mjs 109/109 통과입니다.

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

Labels

None yet

Projects

None yet

1 participant