From adf4f1cd4dc6c76439f4a1a90836da41e77db620 Mon Sep 17 00:00:00 2001 From: hyperpolymath-bot Date: Tue, 22 Sep 2026 15:51:05 +0000 Subject: [PATCH] fix(changelog): break infinite changelog-regenerate loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maa-framework#219 exposed an infinite CI loop: the changelog workflow (mode: pr-back) opens a PR to regenerate CHANGELOG.md; merging that PR adds a new conventional commit (`chore(changelog): regenerate from conventional commits (#NNN)`) which git-cliff picks up under Chores, producing a fresh diff and opening the next PR. The main branch accumulated 21 such commits (#192, #195, #199, #201-#218) before the loop was noticed; PR #220 was closed as part of this hotfix. Three-part fix (defense in depth): 1. **cliff.toml (local override)** — adds a skip = true commit_parser for the bot's own `chore(changelog): regenerate from conventional commits` subjects (placed BEFORE the generic ^chore matcher, since commit_parsers is first-match-wins). This is the primary fix because it survives GitHub squash-merge (which uses the PR title as the new commit subject on main). This local override is an emergency hotfix; the matching canonical fix is in standards#988 and this file should be deleted once that merges. 2. **CHANGELOG.md** — regenerated against the fixed config, which removes the 21 spammy `Regenerate from conventional commits` bullets. No other content changes (verified: 21 deletions, 0 additions). 3. **.github/workflows/changelog.yml** — adds a job-level `if:` guard so the reusable isn't even invoked when the push's head commit message contains [skip changelog]. This complements the reusable's internal guard job (added in standards#988). The canonical fix (standards#988) also hardens the reusable to close older open bot/changelog-* PRs and tag its own commits with [skip changelog]. Once that PR merges, the local cliff.toml can be deleted to re-consume the canonical standards config. Closes #219. [skip changelog] --- .github/workflows/changelog.yml | 10 ++++ CHANGELOG.md | 21 ------- cliff.toml | 98 +++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 cliff.toml diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 1d28c44..4556010 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -32,6 +32,16 @@ permissions: jobs: changelog: # Pinned to standards@main as of 2026-09-21 (commits/changelog: #886). + # NOTE: the reusable checks out cliff.toml from standards@main at + # runtime (not from the pinned SHA), so cliff.toml fixes propagate to + # this caller immediately without a SHA bump — only changes to the + # reusable workflow YAML itself require re-pinning here. uses: hyperpolymath/standards/.github/workflows/changelog-reusable.yml@b77c53c85e5734756a793497845735485c9dc933 with: mode: pr-back + # Loop-prevention defense-in-depth (maa-framework#219 / standards#988): + # don't even invoke the reusable if the push's head commit already + # carries [skip changelog]. The reusable itself has an internal guard + # job and cliff.toml has a subject-matcher skip rule; this is a third + # layer that saves spinning up a runner at all. + if: ${{ !contains(github.event.head_commit.message, '[skip changelog]') }} diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5cddf..39600a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,27 +62,6 @@ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0. ### Chores -- Regenerate from conventional commits (#218) (a60d268) -- Regenerate from conventional commits (#217) (ac24018) -- Regenerate from conventional commits (#216) (692eb24) -- Regenerate from conventional commits (#215) (4aec320) -- Regenerate from conventional commits (#214) (f0f3c6f) -- Regenerate from conventional commits (#213) (68f8f75) -- Regenerate from conventional commits (#212) (e5e89c6) -- Regenerate from conventional commits (#211) (7eeec4a) -- Regenerate from conventional commits (#210) (b1009a7) -- Regenerate from conventional commits (#209) (5d9bd5e) -- Regenerate from conventional commits (#208) (2bc34ab) -- Regenerate from conventional commits (#207) (3f376c9) -- Regenerate from conventional commits (#206) (ddf9670) -- Regenerate from conventional commits (#205) (c066522) -- Regenerate from conventional commits (#204) (d2e435f) -- Regenerate from conventional commits (#203) (818f339) -- Regenerate from conventional commits (#202) (bf40e23) -- Regenerate from conventional commits (#199) (d6011f1) -- Regenerate from conventional commits (#201) (201510f) -- Regenerate from conventional commits (#195) (90b075e) -- Regenerate from conventional commits (#192) (e22eacb) - Remove dead Codecov upload steps + lock pins (#181) (443a4e4) - Migrate root .well-known/ to www/.well-known/ (#175) (1cb4d52) - Relock drifted action pins (#173) (ca810e4) diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..12a5203 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Local override of the canonical hyperpolymath/standards cliff.toml. +# +# This exists ONLY as an emergency hotfix for the infinite changelog loop +# surfaced in maa-framework#219. The canonical config at +# hyperpolymath/standards/templates/cliff.toml was missing a skip rule for +# the bot's own auto-generated commits, which caused each squash-merge of +# a changelog PR to add a new conventional commit, re-trigger +# regeneration, and open the next changelog PR in an infinite chain. +# +# The fix (adding a subject-matcher skip rule for +# "chore(changelog): regenerate from conventional commits") has been +# submitted as standards#988. Once that PR merges, this local override can +# (and should) be deleted so the repo returns to consuming the canonical +# config from standards@main, per estate convention. +# +# This file is byte-identical to the standards/templates/cliff.toml on +# branch fix/changelog-loop-* (standards PR #988), modulo this header +# comment block. +# +# +# Canonical git-cliff config for the hyperpolymath estate. +# +# Place a copy at `cliff.toml` in any repo and run `git cliff -o CHANGELOG.md` +# locally, OR consume the `changelog-reusable.yml` workflow which uses this +# config automatically. +# +# Closes the 65% CHANGELOG gap surfaced by the 2026-05-26 documentation-debt +# audit (standards#197). + +[changelog] +header = """ +# Changelog + +All notable changes to this project are documented here. + +This file is generated by [git-cliff](https://git-cliff.org) from conventional +commits. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) +and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +""" +body = """ +{% if version -%} +## [{{ version | trim_start_matches(pat="v") }}] — {{ timestamp | date(format="%Y-%m-%d") }} +{% else -%} +## [Unreleased] +{% endif -%} + +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group | upper_first }} + +{% for commit in commits -%} +- {{ commit.message | upper_first | trim }}{% if commit.breaking %} **[BREAKING]**{% endif %} ({{ commit.id | truncate(length=7, end="") }}) +{% endfor %} +{% endfor %} +""" +footer = """ + +""" +trim = true + +[git] +conventional_commits = true +filter_unconventional = false +split_commits = false +commit_parsers = [ + { message = "^feat", group = "Added" }, + { message = "^add", group = "Added" }, + { message = "^fix", group = "Fixed" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Changed" }, + { message = "^docs", group = "Documentation" }, + { message = "^test", group = "Tests" }, + { message = "^ci", group = "CI" }, + # Never re-emit the auto-changelog's own commits; that causes an infinite + # regenerate loop (surfaced by maa-framework PR #219). Must come BEFORE + # the generic ^chore matcher because commit_parsers is first-match-wins. + # The regex matches both the bot's branch commit subject and the default + # GitHub squash-merge subject ("... (#NNN)"), which is critical because + # in pr-back mode the squash-merge subject (not the branch commit body) + # survives into main. + { message = "^chore\\(changelog\\):\\s*regenerate from conventional commits", skip = true }, + { body = "\\[skip changelog\\]", skip = true }, + { message = "^chore", group = "Chores" }, + { message = "^style", skip = true }, + { message = "^revert", group = "Reverted" }, + { body = ".*security",group = "Security" }, + # Fallback: any commit not matching above falls into "Other". + { message = ".*", group = "Other" }, +] +filter_commits = false +tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+" +skip_tags = "v0.0.0-test" +ignore_tags = "" +topo_order = false +sort_commits = "newest"