From 0dd56a1251b26ef77e405c0cdafd2a5d98588cab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 5 Jul 2026 16:18:08 +0900 Subject: [PATCH] feat(ci): auto-close pull requests with no net changes Bot authors sometimes open PRs that have commits but a 0-file diff vs. base (GitHub shows "No files changed / +0 -0"). Add a central pull_request_target workflow that closes such empty PRs (leaving drafts alone) so humans do not have to. Uses a write token without checking out PR code, so no code-execution risk. Add close-empty-pr.yml to the org required-workflows list to run per repo. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P --- .github/workflows/close-empty-pr.yml | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/close-empty-pr.yml diff --git a/.github/workflows/close-empty-pr.yml b/.github/workflows/close-empty-pr.yml new file mode 100644 index 0000000..530384a --- /dev/null +++ b/.github/workflows/close-empty-pr.yml @@ -0,0 +1,55 @@ +# Auto-closes pull requests that have commits but no net change vs. their base +# (GitHub shows "No files changed / +0 -0"). The org's bot authors sometimes +# open such empty PRs; this closes them so humans do not have to. +# +# Runs per repo as a central required org workflow. pull_request_target gives a +# write-scoped token (needed to close) without checking out untrusted PR code, +# so there is no code-execution risk — the job only reads PR metadata and closes. +# Drafts are left alone. +name: Close Empty PR + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + pull-requests: write + contents: read + +jobs: + close-empty: + runs-on: ubuntu-latest + steps: + - name: Close PR when it has no net changes + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.event.pull_request.base.repo.full_name }} + PR: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + + # GitHub computes the diff asynchronously; poll briefly for a settled + # changed_files count before deciding (null while still computing). + changed="" + draft="false" + for _ in 1 2 3 4 5 6; do + payload="$(gh api "repos/${REPO}/pulls/${PR}")" + changed="$(jq -r '.changed_files // ""' <<<"$payload")" + draft="$(jq -r '.draft // false' <<<"$payload")" + [ -n "$changed" ] && break + sleep 10 + done + echo "PR #${PR} changed_files=${changed:-unknown} draft=${draft}" + + if [ "$draft" = "true" ]; then + echo "Draft PR — leaving it open." + exit 0 + fi + if [ "$changed" = "0" ]; then + gh pr comment "${PR}" --repo "${REPO}" \ + --body "자동 정리: base 대비 실제 변경(diff)이 0건이라 이 PR을 닫습니다. 변경을 추가한 뒤 reopen하세요." || true + gh pr close "${PR}" --repo "${REPO}" + echo "Closed empty PR #${PR}." + else + echo "PR has ${changed} changed file(s); leaving it open." + fi