Skip to content

Approval-triggered API squash-merge for the release PR (clean commit, all-CI gate) #303

Closed

Description

@Shinrai

Goal

Merge the persistent next → master (and hotfixes → master) release PR via the REST merge API with an explicit commit message, triggered by the maintainer's approval, gated on all CI (not just required checks). This produces a clean, reliable release commit and eliminates two problems with the current manual UI squash:

  1. Body-drop. The mobile "Squash and merge" Default path lands a title-only commit (empty body), stripping the curated changelog + coverage. Supplying commit_message ourselves makes the drop impossible. (Companion to Release notes & tag message are stripped to title-only when the release squash commit omits the PR body #298/fix(release): source release notes from the PR body when the squash drops it #299, which made the Release notes robust; this makes the commit robust.)
  2. Messy co-author trailers. GitHub's squash UI auto-appends Co-authored-by: for every distinct commit author — not deduped by account, not bot-stripped (e.g. cldmv-bot[bot] + the same human under two email variants). That auto-population is a UI/default-composition behavior; a merge-API call with an explicit commit_message uses the message verbatim and adds nothing. The PR body already carries the clean, deduped, bot-free <!-- co-authors --> block (fix(release): credit release-PR contributors via Co-authored-by trailers #292), so it becomes the authoritative credit.

It also sidesteps GitHub's native auto-merge dead-end: enablePullRequestAutoMerge refuses a PR that's already green ("clean status"). A direct PUT …/merge is the opposite — it succeeds precisely when the PR is mergeable.

Trigger

Approval-triggered. The maintainer approves the release PR as they already do; the merge fires once CI is green. A label arm was considered and rejected — CI already stamps release / ! release → master, so an arming label would be confusing.

Caller events: pull_request_review (submitted, state == approved) to arm, plus check_suite: completed (and/or status) to re-evaluate so an approval given before CI finishes still fires when the last check goes green. If a later commit lands on next and the ruleset dismisses the stale approval, the maintainer re-approves — an accepted tradeoff.

Gate (all CI, not just required)

Before merging, enumerate every check-run (/commits/{sha}/check-runs) and commit status (/commits/{sha}/status) on the PR head — the full set, not just the ruleset's required contexts (coverage badge, PR-body refresh, etc. are non-required and must finish first). Merge only when:

  • all are in a terminal state (nothing queued/in_progress) — the merge workflow excludes its own run so it doesn't wait on itself;
  • none failed — success/neutral/skipped pass, any failure/cancelled/timed_out stops and reports (an explicitly-allowed-to-fail check can be whitelisted);
  • an approving review from an allowed association (MEMBER/OWNER) is present.

If not all terminal yet → exit cleanly and wait for the next check-completion event to re-fire. Because the gate waits for the non-required body-refresh job too, the PR body is fully composed by merge time, so reading it fresh for commit_message can't capture a stale (pre-coverage) body — resolving the race raised in the original discussion.

Merge

PUT /repos/{o}/{r}/pulls/{n}/merge with merge_method: "squash", sha: <head>, commit_title: <PR title>, commit_message: <PR body fetched fresh at merge time>. Does not approve as the bot (the human approval is the gate). The bot App token (contents+PR write, allowed to land on master) performs the merge; the maintainer's approval satisfies the ruleset's required review, so no bypass of the human gate.

Shape

  • New reusable workflow reusable-release-merge.yml + job action github/jobs/release-merge + a thin caller template release-merge.yml, wired org-wide like the other release-flow workflows.
  • Reuses the existing scaffolding (create-app-token, the api() helper, the reusable→job structure) but not approveAndEnableAutoMerge (that approves as the bot and uses native auto-merge with a no-commit_message fallback — wrong for a release).
  • Only ever acts on the release PR (head next/hotfixes → base the release branch, carrying the release label); idempotent/benign on races and when already merged.

Consequence for #292

With this in place, keep #292's co-author block — an explicit-message API merge adds nothing of GitHub's own, so the block is exactly what lands in the commit (deduped, bot-free). The revert path (#301) is only relevant if we stay on UI merges.

Activity

  1. added
    area: coreTouches core library / runtime source code
    type: ciChanges to CI workflows, actions, or build pipelines
    on Sep 16, 2026
  2. added theissue type on Sep 16, 2026
  3. cldmv-bot commented on Sep 16, 2026

    @cldmv-bot
    Contributor

    Closed in v4.28.0 — resolved by #304.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreTouches core library / runtime source codetype: ciChanges to CI workflows, actions, or build pipelines

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions