Skip to content

πŸ› [v6] attribution: a description that mentions "β€” hive:" still gets signed #7553

πŸ› [v6] attribution: a description that mentions "β€” hive:" still gets signed

πŸ› [v6] attribution: a description that mentions "β€” hive:" still gets signed #7553

name: Changelog Reminder
# Why this exists
#
# CHANGELOG.md states its own rule β€” "maintainers should add notable user-facing
# changes as PRs land" β€” and that rule quietly stopped being followed: the file
# went eleven days and roughly a hundred and thirty merges without an entry, and
# only a docs-sweep agent noticed. Backfilling it resets the clock but does not
# stop the drift, so this reminds at the one moment somebody can act cheaply:
# while the PR is still open.
#
# It is ADVISORY ON PURPOSE. Most PRs genuinely do not belong in a changelog
# whose own guidance excludes routine refactors, test-only changes and
# dependency churn, so a hard gate would fail honest work and train people to
# ignore it β€” or worse, to add noise entries just to make it green. It comments
# once and always succeeds; the judgement stays with the author.
#
# "Always succeeds" is load-bearing and was not true (#4440): on a fork PR the
# comment POST 403s, because GitHub caps GITHUB_TOKEN at read-only for
# `pull_request` events from forks whatever the permissions: block requests, and
# `bash -e` turned that into a red check on every external contribution. The
# reminder is now delivered as a JOB SUMMARY and a ::notice:: annotation, which
# need no write permission and therefore reach forks and same-repo branches
# alike; the PR comment is attempted on top of that and its failure is
# tolerated. src/deploy/test_changelog_reminder.sh executes the real step script
# against a stubbed `gh` that 403s and asserts it still exits 0.
on:
pull_request:
types: [opened, ready_for_review]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
remind:
name: changelog reminder
# Drafts and bots are excluded: a draft is not ready for this question, and
# dependency bumps are exactly what the changelog guidance excludes.
if: >-
github.event.pull_request.draft == false &&
github.actor != 'dependabot[bot]' &&
github.actor != 'copilot-swe-agent[bot]'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check whether this PR touches CHANGELOG.md or changelog.d/
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Paginate: a large PR can exceed one page, and a missed page would
# produce a false reminder on a PR that did update the changelog.
files="$(gh api --paginate "repos/$REPO/pulls/$PR/files" --jq '.[].filename')"
# A changelog.d/ fragment (#5675) IS the changelog entry now; a
# direct CHANGELOG.md edit still counts during the transition.
if printf '%s\n' "$files" | grep -qxE 'CHANGELOG\.md|changelog\.d/[^/]+\.md'; then
echo "touched=true" >> "$GITHUB_OUTPUT"
else
echo "touched=false" >> "$GITHUB_OUTPUT"
fi
# Docs-only and CI-only changes are not user-facing in the sense the
# changelog means, so they are never worth a reminder.
if printf '%s\n' "$files" | grep -qvE '^(src/docs/|docs/|\.github/|changelog\.d/|README\.md$|CHANGELOG\.md$)'; then
echo "code=true" >> "$GITHUB_OUTPUT"
else
echo "code=false" >> "$GITHUB_OUTPUT"
fi
- name: Remind once
if: steps.check.outputs.touched == 'false' && steps.check.outputs.code == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
run: |
# NOT `set -e` (#4440). This step delivers a REMINDER, and the header
# above promises it "always succeeds" and "never blocks a merge". It
# did neither on a pull request from a FORK: GitHub caps GITHUB_TOKEN
# at read-only for `pull_request` events raised from forks, whatever
# the permissions: block asks for, so the comment POST answered
#
# gh: Resource not accessible by integration (HTTP 403)
#
# and `shell: bash -e` turned that into a failed check. The result was
# the exact thing the header set out to avoid β€” an advisory reminder
# behaving as a gate β€” on every external contribution, which is the
# population least likely to know the convention and most in need of
# the nudge. They got a red check and no reminder.
set -uo pipefail
marker='<!-- changelog-reminder -->'
# Body built in a heredoc rather than inline: the text starts a line
# with '**', which YAML would read as an alias in a plain scalar.
body_file="$(mktemp)"
cat > "$body_file" <<EOF
$marker
**Changelog:** this PR changes code but carries no changelog entry
(no \`changelog.d/\` fragment and no \`CHANGELOG.md\` edit).
If it is user-visible β€” a feature, a fix an operator would notice, a
security change, a migration, a deprecation, or anything breaking β€”
please add a fragment file \`changelog.d/<category>-<pr-or-slug>.md\`
containing exactly your entry (see \`changelog.d/README.md\`; the
category is one of added/changed/deprecated/fixed/security). Please
do not append to \`CHANGELOG.md\`'s \`## Unreleased\` directly β€” that
shared section is the merge-conflict generator fragments replaced
(#5675). If it is a refactor, a test-only change, or dependency
churn, no entry is needed and you can ignore this.
This is a reminder, not a gate; it never blocks a merge.
EOF
# Strip the YAML block indentation the heredoc preserved.
sed -i 's/^ //' "$body_file"
# DELIVER IT SOMEWHERE THAT ALWAYS WORKS, FIRST. The job summary and a
# ::notice:: annotation need no write permission at all, so they reach
# fork PRs and same-repo branches identically. This is the delivery the
# reminder can actually rely on; the PR comment below is a nicety on
# top of it.
{
printf '### Changelog reminder\n\n'
grep -v "^${marker}$" "$body_file"
} >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
echo "::notice title=Changelog reminder::This PR changes code but carries no changelog entry. If it is user-visible, please add a changelog.d/<category>-<slug>.md fragment (see changelog.d/README.md; #5675). Not a gate."
# A fork PR cannot post, so do not try: a 403 in the log invites the
# next reader to debug a permission that is working as designed.
if [ "${IS_FORK:-false}" = "true" ]; then
echo "fork PR β€” GITHUB_TOKEN is read-only here, so the reminder was delivered as a job summary and annotation instead of a comment"
exit 0
fi
# Same-repo PR: a comment is nicer, and only ever once. A reminder that
# reappears on every push is noise, and noise is how the previous
# convention died. A failed dedupe READ must not skip the comment, and
# must not fail the job either.
if gh api --paginate "repos/$REPO/issues/$PR/comments" --jq '.[].body' 2>/dev/null \
| grep -qF "$marker"; then
echo "already reminded"
exit 0
fi
# Tolerated, not asserted. Even on a same-repo PR an org policy can
# narrow GITHUB_TOKEN, and the reminder has already been delivered
# above either way.
if ! gh api "repos/$REPO/issues/$PR/comments" -F body=@"$body_file"; then
echo "::notice::could not post the reminder as a PR comment; it is in this job's summary instead"
fi
exit 0