Files
Jean-Pierre MartineauandClaude Opus 5 2bb543d29b The trailer check reads "Fixes #12" and trusts only a 404
It only recognised trailers written with a colon, so "Fixes #12", which
GitHub closes on just the same, went unchecked, as did fix, fixed, close,
closed, resolve and resolved. The colon is now optional and every closing
keyword counts.

It also treated any failed lookup as a missing issue, so a rate limit or an
outage would have left a public comment on a commit saying a real issue did
not exist. Now only a 404 does that; anything else is a warning in the log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 23:10:27 -04:00

92 lines
4.0 KiB
YAML

name: Issue trailers
# A commit that names an issue is the only thing connecting a fix to the person
# who reported it. GitHub resolves that link silently: `Fixes: #42` against an
# issue that does not exist is not an error, it is nothing at all -- the issue
# stays open, the reporter is never told, and the commit looks like it did the
# job. A typo costs a whole loop and announces itself nowhere.
#
# So this checks the trailers that ARE there rather than nagging about the ones
# that are not. Most commits here are internal work that legitimately closes
# nothing, and a workflow that complains about all of them gets muted in a week.
#
# Cross-repo trailers (`Fixes: owner/repo#12`) are reported but not verified:
# GITHUB_TOKEN only reaches this repository.
on:
push:
branches: [main]
permissions:
contents: write # to leave a comment on the offending commit
issues: read
jobs:
check:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Verify every issue named in a trailer exists
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
COMMITS: ${{ toJSON(github.event.commits) }}
run: |
set -euo pipefail
echo "$COMMITS" > /tmp/commits.json
count=$(jq 'length' /tmp/commits.json)
echo "checking $count commit(s)"
problems=0
for i in $(seq 0 $((count - 1))); do
sha=$(jq -r ".[$i].id" /tmp/commits.json)
msg=$(jq -r ".[$i].message" /tmp/commits.json)
short="${sha:0:8}"
# Trailers only: a line that starts with the keyword. A bare "#12"
# in prose is a reference someone wrote on purpose, not a claim
# that this commit closes anything. The colon is optional, because
# GitHub closes on "Fixes #12" just as it does on "Fixes: #12", and
# the keywords are every form GitHub accepts, plus refs and see.
refs=$(printf '%s\n' "$msg" \
| grep -oiE '^(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved|refs|see)(:[[:space:]]*|[[:space:]]+)([A-Za-z0-9._-]+/[A-Za-z0-9._-]+)?#[0-9]+' \
|| true)
[ -z "$refs" ] && continue
while IFS= read -r ref; do
[ -z "$ref" ] && continue
target=$(printf '%s' "$ref" | sed -E 's/^[A-Za-z]+:?[[:space:]]*//')
num="${target##*#}"
if [ "$target" != "#${num}" ]; then
repo="${target%%#*}"
echo " $short -> $target (other repository, not verified here)"
continue
fi
if state=$(gh api "repos/$REPO/issues/$num" --jq .state 2>/tmp/gh.err); then
echo " $short -> #$num ($state)"
elif ! grep -q 'HTTP 404' /tmp/gh.err; then
# Only a 404 means the issue is missing. Anything else (a rate
# limit, an outage) says nothing about the trailer, and a public
# comment calling a real issue missing would be worse than silence.
echo "::warning::could not check #$num for commit $short: $(head -c 200 /tmp/gh.err)"
else
echo "::error::commit $short names #$num, which does not exist in $REPO"
problems=$((problems + 1))
gh api "repos/$REPO/commits/$sha/comments" \
-f body="This commit's trailer names **#$num**, which does not exist in \`$REPO\`.
GitHub resolves a closing keyword silently, so nothing failed and nothing was linked — the issue this was meant to close is still open, and whoever reported it has not been told. Worth a follow-up commit with the right number, or a note on the issue itself." \
>/dev/null
fi
done <<< "$refs"
done
if [ "$problems" -gt 0 ]; then
echo "::warning::$problems trailer(s) pointed at issues that do not exist"
fi