2026-09-04 · githubactions · github · ci · claudecode
Automatically Update Issue Labels Based on PR Activity
When tracking progress by adding labels like status:in-progress or status:review to Issues, you will inevitably forget to apply them.
Humans forget. AI also forgets. After finishing implementation and opening a PR, the Issue label remains in-progress. The next day, you check and wonder, "Was this already finished?"
The correct approach is to delegate state management to machines. Below is the full workflow I am currently using.
What it does
| Event in PR | Issue Label |
|---|---|
| PR opened | status:review |
| Review with changes requested | status:changes-requested |
| PR merged | status:done |
It identifies the Issue from Closes #<number> in the PR body.
Full Code
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
Pitfalls Encountered
Deleting a non-existent label causes failure
gh issue edit --remove-label fails if the label is not attached.
Since I want to keep status: labels to exactly one at all times, I remove all of them. Without handling this, set -e would cause the script to exit on the first missing label.
gh issue edit "$n" --remove-label "$l" >/dev/null 2>&1 || true
I suppress the error. However, I do not suppress errors for --add-label.
If adding a label fails, it is a real problem, so I let it fail to make it noticeable.
Race conditions with consecutive events
If a review is submitted immediately after opening a PR, two events run almost simultaneously. Both touch the same Issue labels, and depending on the order, an older state might overwrite a newer one.
concurrency:
group: issue-status-${{ github.event.pull_request.number }}
cancel-in-progress: false
I serialize them per PR number. cancel-in-progress: false is critical;
if set to true, the preceding label update is cancelled mid-execution. It is better to wait.
Ignoring raw references like #45
Changing state based on mentions like "Related: #45" causes accidents.
I only capture lines where Closes / Fixes / Resolves precede the number.
Here are the actual verification results:
Closes #12 -> [12]
Fixes #7 and Closes #9 -> [7 9]
Resolved #3 -> [3]
この PR は #45 に関連します -> [] ← Not captured
issue #8 を見てください -> [] ← Not captured
Minimize permissions
The default GITHUB_TOKEN has broad permissions, so I restrict them explicitly.
Only Issue writing is required for this workflow.
Does not work for PRs from forks
The GITHUB_TOKEN in the fork's source repository is read-only, so labels are not updated for external contributor PRs. This is not an issue for personal projects or internal repositories, but if you use this in OSS, you would need to consider pull_request_target (though additional security precautions are required).
Create Labels First
If the labels do not exist, --add-label will fail, so I also script their creation.
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
If creation fails, it falls back to editing, so running it multiple times yields the same result.
Why delegate to machines?
This is part of a setup where I assign multiple personas to Claude Code to drive development. When dividing roles into designer, implementer, and reviewer, you want each persona to handle label operations. However, if you leave this to the personas, they will inevitably forget.
Let machines hold the state, and let personas focus only on "what was done." Since making this distinction, the Issue list no longer diverges from reality.
I have open-sourced the settings for these four personas under the MIT license.
Copy them and run ./setup.sh to get started.
I publish the configuration for splitting Claude Code into separate personas —
Architect, Coder, Reviewer, Conflict Resolver — under MIT. Copy it, run
./setup.sh, and it works. It does not depend on your tech stack.
https://github.com/quintetkit/quartet
I built one real tool using nothing but this workflow. Every Issue, PR, review and merge is still there. The parts that went wrong were not deleted.
https://github.com/quintetkit/mdlinkcheck
The version that adds a UI Designer persona, review criteria, a per-Issue parallel execution script and a 10-chapter guide is on the product page.
The workflow itself is available
Quartet, the four-persona version, is published free under MIT. Quintet adds a UI Designer persona, review criteria, a per-Issue parallel execution script, and a 10-chapter guide.
See the free version Product page