* fix(ci): backport on a label added after merge, not only at merge time
The backport workflow only triggered on pull_request_target closed, so a 'backport release/vX.Y.Z'
label applied AFTER a PR was already merged never created a backport PR (a common slip). Add the
labeled event; the existing 'merged == true' guard still skips still-open PRs, so labelling an
open PR does nothing until it merges, while labelling a merged PR now backports it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): gate the labeled-trigger backport on the labeler's write+ permission
Per review (PerishCode): the new labeled path let a triage-only user trigger the App-token
backport on a merged PR, racing backport-label-guard.yml (which strips unauthorized labels in a
separate workflow). Check the labeler's permission inside this workflow, before checkout/
backport-action: on a labeled event, require github.event.sender to have write+ (fail closed);
the closed/merge path stays gated by the merger's write access. Also only run for backport-*
labels, not every label on a merged PR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(ci): auto-merge clean backports on green CI + Feishu ping on failure
release/v* is PR-protected but has no merge queue and no required status check, so neither
GitHub auto-merge nor enqueuePullRequest applies. backport-automerge.yml reacts to ci.yml
completing for a backport-* branch:
- success + clean (non-draft) PR -> squash-merge with the App token (the push to release/*
re-triggers notify-release-feishu / the nightly)
- failure -> Feishu ping for manual follow-up
- conflict (draft) -> left for a human; release-gate blocks the release until it lands
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): harden backport auto-merge — verify head identity + bind to CI SHA
Per review (PerishCode):
- Only auto-merge PRs authored by the release bot from the same repo (author ==
open-design-release-bot[bot] && !isCrossRepository), so a stranger can't get a branch
named backport-* auto-merged with the App token.
- Bind the merge to the SHA CI actually passed on: gate on head_oid == workflow_run.head_sha
and pass --match-head-commit, so a commit pushed after CI went green is never merged
untested (release/v* has no required status checks to catch it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): restrict backport auto-merge to PR CI runs + gate the Feishu path too
Per review (PerishCode):
- Only run for workflow_run.event == 'pull_request' on a backport-* branch, so a push or
manual dispatch can't reach the App-token / Feishu steps.
- Apply the same author == bot && !isCrossRepository gates to the Feishu failure step, so a
fork / non-bot PR named backport-* can't spam the release group.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(ci): add topology coverage for the backport-automerge trusted workflow
Per review (PerishCode): packaged-smoke-workflow.test.ts registers backport-automerge.yml as a
ci workflow_run consumer and asserts its trust gates — workflow_run.event == pull_request, the
App-token mint, author == bot && !isCrossRepository && non-draft && release/v* base, the
head_oid == workflow_run.head_sha + --match-head-commit binding, and the same identity gates on
the Feishu failure path — so the security posture is covered by tests, not just YAML review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): resolve the backport PR by the CI run's SHA, not just the branch name
Per review (PerishCode): bind the resolved PR to the completed run — select the open PR whose
headRefOid == workflow_run.head_sha (and head branch + release/v* base), so a different/newer PR
on the same branch is never acted on. Topology test asserts the SHA-bound resolution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): resolve backport PR from workflow_run.pull_requests, not branch name
Per review (PerishCode): use the run's authoritative pull_requests association as the source of
truth — select the entry whose base is release/* at exactly workflow_run.head_sha — instead of a
gh pr list --head branch-name lookup. Empty for forks (never auto-merged). Topology test updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(release): 自动化 release 切版 + backport + 准入闸
引入一套基于 GitHub Action 的 release 流程自动化(配套飞书《发布期间 fix PR 处理手册》):
- cut-release: 每周二 09:00(北京)从 main HEAD 切 release/vX.Y.Z + bump 版本 + 建 backport label
- backport: 合进 main 的 PR 带 backport label → 自动 cherry-pick 到 release 分支(冲突转 draft)
- backport-label-guard: 校验贴标人 write+ 权限,否则撤销 label
- release-gate: 发版前检查目标 release 分支无未落地 backport
全部用 GitHub App (open-design-release-bot) token,非 GITHUB_TOKEN,
以便其 push/PR 能正常触发下游 workflow(notify-release-feishu / ci)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(release): translate workflow comments and messages to English
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): validate cut-release version input via env; release-gate fails closed on missing branch
Addresses review (PerishCode):
- cut-release: pass manual version through env + strict x.y.z validation to prevent
shell injection; downstream steps consume validated outputs via env (no inline ${{ }} in run).
- release-gate: verify the release branch exists before checking PRs, so a mistyped /
not-yet-cut branch fails the gate instead of falsely passing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): constrain backport/gate target to the release/vX.Y.Z grammar
Addresses review (PerishCode, round 2):
- backport: tighten label_pattern to ^backport (release/v[0-9]+\.[0-9]+\.[0-9]+)$ so a typo
like 'backport main' / 'backport feat/foo' no longer matches and is never run by the bot.
- release-gate: validate the input branch against ^release/vX.Y.Z before the existence check,
so an arbitrary existing branch (e.g. main) with no open PRs cannot falsely pass the gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): cut-release always checks out main HEAD
Addresses review (PerishCode, round 3): pin actions/checkout to ref: main so a manual
workflow_dispatch from another branch still cuts the release branch from main HEAD, not
from whatever ref the run was started on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>