From 16c8a39c284243b0b4c2ea5a4764f0bb2560e9a3 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Fri, 22 May 2026 07:49:13 +0000 Subject: [PATCH 1/2] [Changelog] Parameterize nightly compile over configurable branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `branches` workflow_dispatch input (CSV, default `develop`) plus a small `resolve-branches` job that splits it into a JSON array, then fan the compile job out across that array via `strategy.matrix`. Each matrix entry checks out its target branch and runs that branch's own `tools/changelog/cli.py` — same code path as the PR gate. Extending the nightly to release branches becomes a one-line edit to the `branches` input default. `fail-fast: false` plus per-branch concurrency keys mean a failure on one branch doesn't block the others, and `gh run rerun --failed` retries only the failed entry. Pattern mirrors `daily-compatibility.yml`'s `setup-versions` -> matrix shape. For one-off off-cycle compiles: gh workflow run nightly-changelog.yml \ -f branches=release/3.0.0-beta2 --- .github/workflows/nightly-changelog.yml | 132 +++++++++++++++++++----- 1 file changed, 104 insertions(+), 28 deletions(-) diff --git a/.github/workflows/nightly-changelog.yml b/.github/workflows/nightly-changelog.yml index baf42b4feac7..a15ecae8adaf 100644 --- a/.github/workflows/nightly-changelog.yml +++ b/.github/workflows/nightly-changelog.yml @@ -6,16 +6,22 @@ # Nightly auto-compile: rolls accumulated fragments under # ``source//changelog.d/`` into per-package ``CHANGELOG.rst`` entries, # bumps each ``extension.toml``, deletes consumed fragments, and pushes the -# result back to ``develop``. Keeps the develop branch's changelog current -# without requiring a maintainer to run ``compile`` by hand. +# result back to each branch in the configured list. Keeps every tracked +# branch's changelog current without requiring a maintainer to run +# ``compile`` by hand. # # Scheduled workflow — must live on the default branch (``main``) for the # cron to register. See ``.github/workflows/README.md``. # +# Adding a branch to the nightly set is a one-line edit to ``env.CRON_BRANCHES`` +# below. Each target branch uses its own ``tools/changelog/cli.py`` (the +# same copy the PR gate already runs), so the nightly compile honours the +# same rules that validated the fragments. +# # The push uses a short-lived GitHub App installation token minted from # ``CHANGELOG_APP_ID`` + ``CHANGELOG_APP_PRIVATE_KEY`` (repo secrets). The # App must be installed on this repository with ``contents: write`` and -# added to the bypass-actor list of ``develop``'s branch ruleset so the +# added to the bypass-actor list of each target branch's ruleset so the # auto-commit can push directly without satisfying required-checks / # required-approval gates. # Commits signed by an App token (unlike ``GITHUB_TOKEN``) are treated as @@ -24,6 +30,14 @@ name: Nightly Changelog Compilation +# Branches the nightly cron compiles. Single source of truth — append a +# ref here to extend the nightly set (the active release branch belongs +# here). Each branch must carry ``tools/changelog/cli.py`` and the +# isaaclab-bot App must be in its branch-ruleset bypass list. +# Surrounding whitespace per entry is stripped by the resolver below. +env: + CRON_BRANCHES: develop,release/3.0.0-beta2 + on: schedule: # Run nightly at 5 AM UTC (one hour after daily-compatibility, so we @@ -31,35 +45,87 @@ on: - cron: '0 5 * * *' workflow_dispatch: inputs: + branch: + # Manual trigger is always a single branch. Free-text on purpose + # — scales to any branch (e.g. a new ``release/*`` cut from + # develop) without needing to update the workflow. A branch + # that lacks ``tools/changelog/cli.py`` fails the compile step + # with a clear error, which is the desired failure mode. + description: 'Branch to compile (e.g. develop, release/3.0.0-beta2). Must carry tools/changelog/cli.py.' + required: true + type: string dry_run: description: 'Preview only — do not commit / push' required: false type: boolean default: false -concurrency: - # Only one nightly compile may be in flight at a time. ``cancel-in-progress`` - # is intentionally false: if a previous run is still finishing its push, we - # queue rather than abort it mid-commit. - group: nightly-changelog - cancel-in-progress: false - permissions: # Reduced: the App installation token below carries its own write scope. # GITHUB_TOKEN only needs read access for the standard checkout machinery. contents: read jobs: + resolve-branches: + # CSV → JSON array bridge. ``workflow_dispatch`` inputs can only be + # string / bool / choice, so the branch list arrives as a comma- + # separated string; the matrix below needs a JSON list to fan out. + # Mirrors the ``setup-versions`` job in ``daily-compatibility.yml``. + name: Resolve branch list + runs-on: ubuntu-latest + timeout-minutes: 1 + outputs: + branches: ${{ steps.b.outputs.branches }} + steps: + - id: b + env: + # Schedule → the CRON_BRANCHES list. Manual → the single branch + # the maintainer entered. The two paths are intentionally + # asymmetric: cron is the configured set, manual is exactly one + # branch (required input). + BRANCHES: ${{ github.event_name == 'schedule' && env.CRON_BRANCHES || inputs.branch }} + # ``EVENT_NAME`` mirrors ``github.event_name`` so the guard below + # branches on it without re-interpolating into the shell. + EVENT_NAME: ${{ github.event_name }} + run: | + # CSV → JSON array, trimming surrounding whitespace per entry. + # Manual produces a 1-element array; cron produces N elements. + arr=$(echo "$BRANCHES" | tr ',' '\n' | xargs -n1 | jq -R . | jq -s -c .) + # Manual trigger contract: exactly one branch. A maintainer who + # pastes a comma-separated list into the dispatch form should + # see a clear error, not a silent multi-branch fan-out. + if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$(echo "$arr" | jq 'length')" -ne 1 ]; then + echo "::error::Manual trigger accepts exactly one branch; got $arr. Fire the workflow separately per branch." + exit 1 + fi + echo "branches=$arr" >> "$GITHUB_OUTPUT" + echo "Resolved branches: $arr" + compile-changelog: - name: Compile changelog fragments + name: Compile changelog fragments (${{ matrix.branch }}) + needs: resolve-branches runs-on: ubuntu-latest timeout-minutes: 10 + strategy: + # Independent branches: one failing shouldn't cancel the others. + # Each matrix entry shows up as a separate job tile in the Actions + # UI, so ``release/3.0.0-beta2`` failing doesn't hide ``develop``'s + # success (and ``gh run rerun --failed`` re-runs only the failed + # entry). Mirrors ``daily-compatibility.yml``'s matrix style. + fail-fast: false + matrix: + branch: ${{ fromJson(needs.resolve-branches.outputs.branches) }} + concurrency: + # Per-branch group: two runs against the same ref queue, but + # different refs (develop and a release branch) compile in parallel. + group: nightly-changelog-${{ matrix.branch }} + cancel-in-progress: false steps: # Mint a short-lived (1 h) installation access token for the - # ``isaaclab-bot`` GitHub App. The App is on develop's branch-ruleset - # bypass list, so its push lands without needing the standard - # required-checks / required-approval pipeline. + # ``isaaclab-bot`` GitHub App. The App is on each target branch's + # ruleset bypass list, so its push lands without needing the + # standard required-checks / required-approval pipeline. - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 id: app-token with: @@ -74,12 +140,12 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - # Operate on develop, not the repo's default branch. Scheduled - # workflows fire from the default branch's workflow file by - # default, but we want the *checkout* to be develop so the - # compile sees develop's accumulated fragments and the push - # writes back to develop. - ref: develop + # Operate on the target branch, not the repo's default branch. + # Scheduled workflows fire from the default branch's workflow + # file, but the *checkout* is the branch we're compiling so the + # compile sees that branch's accumulated fragments and the push + # writes back to it. + ref: ${{ matrix.branch }} # App token (vs. GITHUB_TOKEN) means the push is signed by # ``isaaclab-bot`` — the bypass identity — and downstream CI # workflows DO trigger on the resulting commit. @@ -103,6 +169,14 @@ jobs: - name: Commit and push if fragments were compiled if: ${{ !inputs.dry_run }} + env: + # Pass the matrix branch through an env var rather than + # interpolating ``${{ matrix.branch }}`` directly into ``run:``. + # The interpolation happens *before* shell quoting, so an + # adversarial input could escape the surrounding quotes; the + # env passthrough keeps the value inside the shell's variable + # space where standard quoting protects it. + TARGET_BRANCH: ${{ matrix.branch }} run: | # Author commits as the App's bot user so the GitHub UI attributes # them correctly. ID 282401363 is isaaclab-bot[bot]'s user ID. @@ -140,12 +214,14 @@ jobs: done } > "$MSG_FILE" git commit -F "$MSG_FILE" - # Rebase onto develop's current tip in case a human commit - # landed during this run (~2 min window between checkout and - # push). Without this the push fails non-fast-forward and the - # batch waits for the next run. - git pull --rebase origin develop - # Push explicitly to develop so we don't accidentally write - # to the source ref of a workflow_dispatch run. - git push origin HEAD:develop + # Rebase onto the target branch's current tip in case a human + # commit landed during this run (~2 min window between + # checkout and push). Without this the push fails non-fast- + # forward and the batch waits for the next run. ``refs/heads/`` + # is explicit so a same-named tag (if one ever exists) can't + # disambiguate the wrong way. + git pull --rebase origin "refs/heads/$TARGET_BRANCH" + # Push explicitly to the target branch so we don't accidentally + # write to the source ref of a workflow_dispatch run. + git push origin "HEAD:refs/heads/$TARGET_BRANCH" fi From a211e5ab39e3a052f9fc9f3f0f6a2323f4983da8 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Wed, 27 May 2026 21:55:53 +0000 Subject: [PATCH 2/2] [Changelog] Add cli.py existence gate to nightly compile A branch in env.CRON_BRANCHES (or the dispatch input) that lacks tools/changelog/cli.py used to fail implicitly inside the compile step with a python3 file-not-found error. Add an explicit Verify step right after checkout that fails the matrix entry early with a named error. The overall run still reports FAILURE while sibling branches finish in parallel (fail-fast: false already set). --- .github/workflows/nightly-changelog.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly-changelog.yml b/.github/workflows/nightly-changelog.yml index a15ecae8adaf..839130ffccec 100644 --- a/.github/workflows/nightly-changelog.yml +++ b/.github/workflows/nightly-changelog.yml @@ -49,8 +49,8 @@ on: # Manual trigger is always a single branch. Free-text on purpose # — scales to any branch (e.g. a new ``release/*`` cut from # develop) without needing to update the workflow. A branch - # that lacks ``tools/changelog/cli.py`` fails the compile step - # with a clear error, which is the desired failure mode. + # that lacks ``tools/changelog/cli.py`` fails the verify step + # below with a clear error, which is the desired failure mode. description: 'Branch to compile (e.g. develop, release/3.0.0-beta2). Must carry tools/changelog/cli.py.' required: true type: string @@ -154,6 +154,20 @@ jobs: # time via ``git log --diff-filter=A --first-parent``. fetch-depth: 0 + - name: Verify changelog tooling exists on target branch + env: + TARGET_BRANCH: ${{ matrix.branch }} + run: | + # Loud-fail this matrix entry (not the whole run — ``fail-fast: + # false`` keeps siblings going) when the target branch lacks + # ``tools/changelog/cli.py``. A red tile is the desired signal: + # a branch in the nightly set without the compile tooling is a + # configuration error, not a "no-op" condition. + if [ ! -f tools/changelog/cli.py ]; then + echo "::error::Branch '$TARGET_BRANCH' is missing tools/changelog/cli.py — drop it from env.CRON_BRANCHES (or the dispatch input) or restore the tooling on that branch." + exit 1 + fi + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12"