2026-09-04 · githubactions · github · ci · claudecode
PRの動きに合わせてIssueのラベルを自動で張り替える
Issue に status:in-progress や status:review のようなラベルを付けて進捗を追う運用、
やってみると必ず付け忘れます。
人間も忘れますし、AI にやらせても忘れます。実装が終わって PR を出した後、
Issue のラベルが in-progress のまま残っている。翌日見て「これ終わってたっけ」となる。
状態管理は機械に寄せるのが正解でした。実際に使っているワークフローを全文載せます。
やること
| PR で起きたこと | Issue のラベル |
|---|---|
| PR が作られた | status:review |
| レビューで changes requested | status:changes-requested |
| PR がマージされた | status:done |
PR 本文の Closes #<番号> から Issue を特定します。
全文
name: issue-status
on:
pull_request:
types: [opened, reopened, ready_for_review, closed]
pull_request_review:
types: [submitted]
permissions:
contents: read
issues: write
pull-requests: read
concurrency:
group: issue-status-${{ github.event.pull_request.number }}
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Sync label on the linked issue
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_MERGED: ${{ github.event.pull_request.merged }}
ACTION: ${{ github.event.action }}
REVIEW_STATE: ${{ github.event.review.state }}
run: |
set -euo pipefail
issues="$(printf '%s' "${PR_BODY:-}" \
| grep -oiE '(close[sd]?|fixe?[sd]?|resolve[sd]?)[[:space:]]*#[0-9]+' \
| grep -oE '[0-9]+' \
| sort -u || true)"
if [ -z "$issues" ]; then
echo "No linked issue in the PR body. Nothing to do."
exit 0
fi
target=""
if [ "$ACTION" = "closed" ]; then
if [ "$PR_MERGED" = "true" ]; then target="status:done"; fi
elif [ "$ACTION" = "submitted" ]; then
case "${REVIEW_STATE:-}" in
changes_requested) target="status:changes-requested" ;;
esac
else
target="status:review"
fi
[ -z "$target" ] && { echo "No state change for this event"; exit 0; }
all="status:planned status:in-progress status:review \
status:changes-requested status:conflict status:done"
for n in $issues; do
echo "Issue #${n} -> ${target}"
gh issue edit "$n" --repo "$REPO" --add-label "$target" >/dev/null
for l in $all; do
[ "$l" = "$target" ] && continue
gh issue edit "$n" --repo "$REPO" --remove-label "$l" >/dev/null 2>&1 || true
done
done
ハマったところ
付いていないラベルを消そうとすると落ちる
gh issue edit --remove-label は、そのラベルが付いていないと失敗します。
status: を常に1つだけに保ちたいので全部消しにいくのですが、そのままだと
最初の1つで set -e に引っかかって死にます。
gh issue edit "$n" --remove-label "$l" >/dev/null 2>&1 || true
握りつぶします。ただし --add-label のほうは握りつぶしません。
そこが失敗したら本当に問題なので、落として気づけるようにします。
イベントが連続すると競合する
PR を作ってすぐレビューが付くと、2つのイベントがほぼ同時に走ります。 どちらも同じ Issue のラベルを触るので、順番によっては古い状態が後から上書きされます。
concurrency:
group: issue-status-${{ github.event.pull_request.number }}
cancel-in-progress: false
PR 番号ごとに直列化します。cancel-in-progress: false が重要で、
true にすると先行のラベル更新が途中でキャンセルされます。 待たせるのが正解です。
#45 のような素の参照を拾わない
「関連: #45」のような言及で状態を変えてしまうと事故ります。
Closes / Fixes / Resolves が前に付いているものだけを拾います。
実際に検証した結果です。
Closes #12 -> [12]
Fixes #7 and Closes #9 -> [7 9]
Resolved #3 -> [3]
この PR は #45 に関連します -> [] ← 拾わない
issue #8 を見てください -> [] ← 拾わない
権限は最小にする
既定の GITHUB_TOKEN は広い権限を持つので明示的に絞ります。
このワークフローに書き込みが要るのは Issue だけです。
fork からの PR では動かない
fork 元の GITHUB_TOKEN は読み取り専用になるため、外部コントリビューターの
PR ではラベルが更新されません。個人プロジェクトや社内リポジトリなら問題に
なりませんが、OSS で使うなら pull_request_target を検討することになります
(ただしセキュリティ上の注意が別途必要です)。
ラベルを先に作る
ラベルが無いと --add-label が失敗するので、作成もスクリプトにしておきます。
labels=(
"status:planned|ededed|Issue created, not started"
"status:in-progress|1d76db|Implementation in progress"
"status:review|fbca04|PR opened, waiting for review"
"status:changes-requested|d93f0b|Changes requested"
"status:conflict|b60205|Waiting for conflict resolution"
"status:done|0e8a16|Merged and closed"
)
for entry in "${labels[@]}"; do
IFS='|' read -r name color desc <<< "${entry}"
gh label create "${name}" --color "${color}" --description "${desc}" 2>/dev/null \
|| gh label edit "${name}" --color "${color}" --description "${desc}"
done
作成に失敗したら編集にフォールバックするので、何度実行しても同じ結果になります。
なぜ機械に寄せるのか
これは、Claude Code に複数の人格を分担させて開発を回す構成の一部として使っています。 設計者・実装者・レビュアーと役割を分けると、それぞれにラベル操作までやらせたく なります。 しかし人格に任せると、必ず付け忘れます。
状態は機械が持ち、人格は「何をしたか」だけに集中させる。この分け方にしてから、 Issue の一覧が実態とずれなくなりました。
このワークフローを含む4人格ぶんの設定を MIT で公開しています。
コピーして ./setup.sh を叩けば動きます。
Claude Code に設計・実装・レビューを別々の人格として分担させ、GitHub Issue と
ブランチを軸に並列開発を回すための設定一式を MIT で公開しています。
コピーして ./setup.sh を叩けば動きます。技術スタックには依存しません。
https://github.com/quintetkit/quartet
このワークフローだけで実際にツールを 1 つ作りました。Issue の分割から PR、 レビュー、マージまで記録が全部残っています。うまくいかなかった箇所も消していません。
https://github.com/quintetkit/mdlinkcheck
UI 設計人格・レビュー基準・Issue 単位の並列実行スクリプト・実践ガイド 10 章を 足したものは製品ページにあります。