ci: Auto-correct mismatched PR numbers in news fragments#11215
ci: Auto-correct mismatched PR numbers in news fragments#11215
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the existing CI/news-fragment automation so that scripts/assign-pr-number.py not only fills in missing PR numbers for placeholders (e.g., .fix.md), but also auto-corrects mismatched numbered fragments (e.g., 9999.fix.md) by renaming them to the current PR number. It also documents the expected changes/ filename convention.
Changes:
- Update
scripts/assign-pr-number.pyto detect numbered fragments whose prefix doesn’t match the current PR number and rename them to<PR>.<type>.md. - Document the
changes/filename rule (PR number prefix) and CI behavior inchanges/README.md. - Add a
.misc.mdnews fragment describing the CI behavior change.
Reviewed changes
Copilot reviewed 15 out of 19 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/assign-pr-number.py | Adds “mismatched PR number” detection and renaming to enforce fragment naming consistency. |
| changes/README.md | Documents the fragment filename convention and CI auto-renaming behavior. |
| changes/.misc.md | News fragment entry describing the new auto-correction behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for original_filename, news_type in mismatched_fragments: | ||
| numbered_filename = f"{pr_number}.{news_type}.md" | ||
| file_path = base_path / original_filename | ||
| file_path.rename(base_path / numbered_filename) | ||
| print( |
There was a problem hiding this comment.
Both the unnumbered and mismatched rename paths call Path.rename() without checking whether the destination filename already exists (e.g., changes/<PR>.fix.md already present). On POSIX this can overwrite the existing fragment (data loss); on some platforms it can raise and fail the workflow. Please guard against collisions (including two fragments of the same type in one PR) by checking for an existing destination and exiting non-zero with a clear message (or choosing a deterministic non-colliding filename).
|
|
||
| ### Filename convention | ||
|
|
||
| News fragments live in `changes/` and must be named `<PR-number>.<type>.md`, where `<type>` is one of the towncrier types declared under `[tool.towncrier]` in `pyproject.toml` (e.g., `fix`, `feature`, `breaking`, `misc`). |
There was a problem hiding this comment.
The README points to types being declared under [tool.towncrier] in pyproject.toml, but the actual convention used by the script is the [[tool.towncrier.type]] entries (and it uses each entry’s directory field). Consider updating this wording to match the real config structure so contributors know exactly where the allowed values come from.
| News fragments live in `changes/` and must be named `<PR-number>.<type>.md`, where `<type>` is one of the towncrier types declared under `[tool.towncrier]` in `pyproject.toml` (e.g., `fix`, `feature`, `breaking`, `misc`). | |
| News fragments live in `changes/` and must be named `<PR-number>.<type>.md`, where `<type>` matches one of the `directory` values defined by the `[[tool.towncrier.type]]` entries in `pyproject.toml` (e.g., `fix`, `feature`, `breaking`, `misc`). |
When an author (or an AI coding agent) writes `changes/<N>.<type>.md` with an issue number or any other wrong identifier in place of the pull request number, the `assign-pr-number` workflow previously treated it as already-numbered and left it untouched, so the wrong number leaked into the release notes. Extend `scripts/assign-pr-number.py` to rename a numbered news fragment whose prefix does not match the current PR number, reusing the same rename path as unnumbered fragments. Scope the rewrite to fragments added or modified by this PR (computed from `git diff --diff-filter=AM origin/$GITHUB_BASE_REF...HEAD`) so historical fragments accumulated on the base branch from past merged PRs are never touched. Fall back to the original behavior when `GITHUB_BASE_REF` is absent (local runs): rename only unnumbered fragments, leave every numbered fragment alone. Bump `fetch-depth` in the workflow from 2 to 0 so the base ref is reachable for the diff. Document the filename convention explicitly in `changes/README.md`. Related: #11214
5ef9173 to
429a347
Compare
.misc.md -> 11215.misc.md Co-authored-by: octodog <mu001@lablup.com>
ReviewThe scoping via Strengths
FindingsBlocker — silent data loss on rename collision ( Three concrete collision cases, all producing silent content loss:
I just verified the overwrite behavior locally ( Should-fix — follow-up PRs that modify a historical fragment get renumbered ( Should-fix — unguarded Should-fix — Nit — Nit — Nit — type hint shadowing ( Nit — no unit tests ( Open questions for the author
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "git", | ||
| "diff", | ||
| "--name-only", | ||
| "--diff-filter=AM", | ||
| f"origin/{base_ref}...HEAD", | ||
| "--", | ||
| str(base_path), | ||
| ], |
There was a problem hiding this comment.
git diff --diff-filter=AM excludes renamed fragments (status R). If a contributor renames a fragment in the PR (or a fragment is detected as renamed by git), it won't be included in pr_touched_fragments and the script will skip validating/renaming it in CI. Consider including renames (e.g., --diff-filter=AMR) or using --name-status to handle R entries.
|
|
||
| News fragments live in `changes/` and must be named `<PR-number>.<type>.md`, where `<type>` is one of the towncrier types declared under `[tool.towncrier]` in `pyproject.toml` (e.g., `fix`, `feature`, `breaking`, `misc`). | ||
|
|
||
| The number in the filename must be the **pull request number** — not an issue number, Linear/Jira ticket, or any other identifier. When you don't yet know the PR number, leave it empty and commit a placeholder such as `.fix.md` or `fix.md`; the `assign-pr-number` CI workflow will fill in the PR number on push. The same workflow also rewrites any numbered fragment whose prefix does not match the current PR number, so mistakenly using an issue number is recoverable. |
There was a problem hiding this comment.
This description of the CI behavior is broader than what the script now does: mismatched numbered fragments are only rewritten when GITHUB_BASE_REF is set and only for fragments added/modified by the current PR (per the diff-based scoping). Consider updating this paragraph to reflect those constraints so readers don't assume all mismatched fragments are rewritten in all contexts (including local runs).
| The number in the filename must be the **pull request number** — not an issue number, Linear/Jira ticket, or any other identifier. When you don't yet know the PR number, leave it empty and commit a placeholder such as `.fix.md` or `fix.md`; the `assign-pr-number` CI workflow will fill in the PR number on push. The same workflow also rewrites any numbered fragment whose prefix does not match the current PR number, so mistakenly using an issue number is recoverable. | |
| The number in the filename must be the **pull request number** — not an issue number, Linear/Jira ticket, or any other identifier. When you don't yet know the PR number, leave it empty and commit a placeholder such as `.fix.md` or `fix.md`; the `assign-pr-number` CI workflow can fill in the PR number for fragments in PR-context runs. In the same context, it can also rewrite a numbered fragment whose prefix does not match the current PR number, but only for fragments added or modified by the current PR, so mistakenly using an issue number there is recoverable. |
… renumbering Two follow-ups to the news-fragment auto-correction workflow based on review: 1. ``Path.rename`` silently overwrites an existing destination on POSIX, so a PR carrying two fragments of the same type (e.g., ``.fix.md`` plus ``9999.fix.md``, or two mismatched ``9999.fix.md`` and ``8888.fix.md``) would lose one fragment's contents without any error. Introduce ``rename_fragment_or_exit`` which aborts with a clear message when the target already exists, and route both the unnumbered-fragment loop and the mismatched-fragment loop through it. 2. ``git diff --diff-filter=AM`` accepts ``M``-status files, which means a PR that merely edits a historical fragment in place (for example a typo fix on a merged fragment prior to release) would have its original PR-number prefix rewritten to the editing PR's number. Narrow the filter to ``A`` so only newly added fragments become rename candidates; rename the helper to ``get_pr_added_fragments`` to match.
…ront - Skip mismatch rewrites on release-branch backports so fragments keep linking to the original main PR in release notes (``GITHUB_BASE_REF == "main"``). - Collect all planned renames before touching disk; reject collisions (with existing files or within the plan) before any partial rename. - Align ``changes/README.md`` with the actual towncrier config (``[[tool.towncrier.type]]`` + ``directory``) and scope the auto-rewrite description to main PRs / PR-added fragments. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
LGTM ✅ Post-polish (
Verified locally against 10 scenarios (main-PR mixed renames, backport preservation, local fallback, same-type collisions with disk and within plan, mismatch vs. correct collision, no-op paths, invalid filenames). CI is green, including the |
Summary
scripts/assign-pr-number.pyso theassign-pr-numberworkflow also rewrites news fragments whose filename prefix does not match the current PR number (e.g.,9999.fix.mdwritten with an issue number instead of a PR number), not only unnumbered placeholders like.fix.md.git diff --diff-filter=AM origin/$GITHUB_BASE_REF...HEAD), so historical fragments accumulated on the base branch from past merged PRs are never touched.fetch-depthfrom2to0so the base ref is reachable for the diff.changes/README.md: the leading number must be the pull request number, and unnumbered drafts are acceptable because CI will fill it in on push.Resolves #11214. Builds on the numbering automation originally introduced in #989.
Behavior
For files in
changes/, runningpython scripts/assign-pr-number.py <PR>under CI (withGITHUB_BASE_REFset):.fix.md(added by this PR, unnumbered)<PR>.fix.md<PR>.fix.md(unchanged)<PR>.fix.md(added by this PR, correct)9999.fix.md(added by this PR, wrong number)<PR>.fix.md(new)11170.fix.md(from a past merged PR, on base branch)Local runs without
GITHUB_BASE_REFfall back to the pre-existing behavior: rename unnumbered placeholders, leave all numbered fragments alone.Test plan
11170.fix.md) present on base + this-PR's.doc.md,12345.feature.md,9999.fix.md— only the unnumbered and the mismatched fragments are renamed; the historical one is left alone.GITHUB_BASE_REF) — mismatched fragment is preserved; only unnumbered is renamed.already exists, emitshas_renamed_pairs=false.pants --changed-since=HEAD --changed-dependents=transitive fmt lint checkpasses.assign-pr-numberjob ran on this PR's first push and correctly renamedchanges/.misc.md→changes/11215.misc.md.Notes for reviewers
One open question: backport PRs. A backport to a release branch typically lands with the fragment already numbered after the original
mainPR (e.g., copying11170.fix.mdinto the backport branch). Because backport PRs do touchchanges/11170.fix.mdin their diff, this change would rewrite that prefix to the backport PR's number. If the project convention is to preserve the original PR number in release-branch fragments, the rewrite should be gated further (e.g., only whenGITHUB_BASE_REF == "main"). Current behavior onmainis unaffected.