Skip to content

[ML] Auto-approve backport PRs so auto-merge can complete#3094

Merged
edsavage merged 4 commits into
elastic:mainfrom
edsavage:feature/auto-approve-backports
Jul 24, 2026
Merged

[ML] Auto-approve backport PRs so auto-merge can complete#3094
edsavage merged 4 commits into
elastic:mainfrom
edsavage:feature/auto-approve-backports

Conversation

@edsavage

@edsavage edsavage commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Backport PRs opened by the auto-backport workflow have auto-merge enabled but never merge (e.g. #3092). Two independent things blocked them:

  1. No CI. buildkite-pr-bot only triggers ml-cpp-pr-builds for a PR whose sender is org-allowed: it matches an allowed_list entry, has admin/write on the repo, or is an org member (allow_org_users). Bot accounts are never org members and apps don't match the write-permission path, so a bot-authored backport got no build.
  2. No approval. The release branches require one approving review via the org-wide ruleset [org] Require a PR (id 221255, source: elastic), which has no bypass actors. A PR authored by github-actions[bot] cannot be approved by github-actions[bot] (GitHub blocks self-approval), so the required review was never satisfied and auto-merge stayed BLOCKED.

The CI-trigger half (1) for github-actions[bot] is already fixed and validated by #3103 (allowed_list: ["github-actions[bot]"], #3099). This PR fixes the approval half (2) and completes the CI-trigger config for the ephemeral-token author.

What this PR changes

Adopts the production-proven author-with-ephemeral-token / approve-with-GITHUB_TOKEN pattern (used by elastic/cloud, elastic/elasticsearch-ruby and the ECP GitOps repos such as elastic/catalog-sync-config):

  • backport.yml: mints a short-lived token from Elastic's central ephemeral-token service (elastic/ci-gh-actions/fetch-github-token@v1, ci-prod, via OIDC->Vault) before the backport action and passes it as the action's github_token, so the backport PRs are authored by the ephemeral-token identity (elastic-vault-github-plugin-prod[bot]) — a distinct identity from github-actions[bot]. It then approves each backport PR with GITHUB_TOKEN (github-actions[bot]) — a different identity from the author, so the approval counts — and enables auto-merge (squash). Adds id-token: write for OIDC.
  • .buildkite/pull-requests.json: adds elastic-vault-github-plugin-prod[bot] to allowed_list, so buildkite-pr-bot triggers CI on the ephemeral-authored backport PRs. Combined with [ML] Allow github-actions[bot] to trigger ml-cpp-pr-builds CI #3103's github-actions[bot] entry, this matches elastic/elasticsearch's allow-list exactly (both bots).
  • Safe no-op before the Token Policy is registered: the token fetch uses continue-on-error and the workflow falls back to authoring with GITHUB_TOKEN (github-actions[bot], which [ML] Allow github-actions[bot] to trigger ml-cpp-pr-builds CI #3103 already allow-lists for CI); auto-approval is skipped with a warning and the backport awaits a manual approval.

Why this counts (verified, not assumed)

elastic/catalog-sync-config merges renovate PRs under the identical org-only ruleset (221255 + 1225254; count: 1, require_code_owner_review: false, require_last_push_approval: false — same as ml-cpp): a PR authored by one bot and approved by github-actions[bot] (a non-author identity) shows reviewDecision: APPROVED and auto-merges (e.g. elastic/catalog-sync-config#83, elastic/catalog-info#4264). ml-cpp already has Allow GitHub Actions to create and approve pull requests enabled (can_approve_pull_request_reviews: true). GitHub's only relevant restriction is self-approval, which the author != approver split avoids.

The CI-trigger half was validated empirically: with #3103 live on main, a throwaway PR authored by github-actions[bot] auto-started ml-cpp-pr-builds (creator elasticmachine) within ~20s.

Required to activate (not in this repo)

Register the GitHub Token Policy in elastic/catalog-info (elastic/catalog-info#4281), scoped as:

  • bound_claims.workflow_ref: elastic/ml-cpp/.github/workflows/backport.yml@*
  • permissionset.additional_permissions: { contents: write, pull_requests: write } (contents to push the backport branch, pull_requests to open the PR), selector repository: elastic/ml-cpp
  • owner: group:ml-core

Notes

  • The earlier approach (approve with the ephemeral token) relied on an app holding only pull_requests: write being an eligible approver, which has no production precedent in the org. This pattern instead approves with GITHUB_TOKEN, which is proven to count.
  • elastic/ci-gh-actions/fetch-github-token@v1 is used by tag; happy to pin to a SHA if org policy requires it.

Test plan

  • Merge with the Token Policy unregistered -> a subsequent backport still opens the PR (authored by github-actions[bot]), gets CI (via [ML] Allow github-actions[bot] to trigger ml-cpp-pr-builds CI #3103), and arms auto-merge; logs the "ephemeral token unavailable" warning; does not fail.
  • Register the Token Policy (#4281), trigger a backport, and confirm the PR is authored by elastic-vault-github-plugin-prod[bot], CI runs on it (via the new allowed_list entry), github-actions[bot] approves it, and it merges once checks pass.

Made with Cursor

edsavage and others added 4 commits July 24, 2026 14:02
Backport PRs already have auto-merge enabled, but they never merge because the
release branches require one approving review via an org-wide ruleset ("[org]
Require a PR") that has no bypass actors. The bot that opens the backport
(github-actions[bot]) cannot approve its own PR, so the review requirement is
never satisfied.

Mirror the Elasticsearch auto-backport-and-merge model: mint a short-lived
token for a dedicated backport-approver GitHub App (a distinct, non-author
identity) and have it approve each backport PR, after which the existing
auto-merge step completes once required checks pass.

The approval step is skipped when the App credentials are not configured, so
this is a safe no-op until ML_BACKPORT_APPROVER_APP_ID (variable) and
ML_BACKPORT_APPROVER_PRIVATE_KEY (secret) are set.

Co-authored-by: Cursor <cursoragent@cursor.com>
Rework the auto-approve step to mint a short-lived token from Elastic's central
ephemeral-token GitHub App (elastic/ci-gh-actions/fetch-github-token, OIDC ->
Vault) instead of a bespoke, self-owned GitHub App, which we cannot get approved
to create. The ephemeral-token identity approves each clean, bot-authored
backport so the org-wide "[org] Require a PR" review requirement is satisfied and
the existing auto-merge can complete.

The token fetch uses continue-on-error so the workflow stays safe until the
backport Token Policy is registered in elastic/catalog-info: the approval is
skipped with a warning and backports simply await a manual approval.

Requires: a TokenPolicy (pull_requests: write, scoped to elastic/ml-cpp, bound to
this workflow ref) registered in elastic/catalog-info, and confirmation from
Platform Engineering Productivity that an ephemeral-token approval counts toward
the required review.

Co-authored-by: Cursor <cursoragent@cursor.com>
…h-GITHUB_TOKEN

Adopt the production-proven pattern used by elastic/cloud, elasticsearch-ruby
and the ECP GitOps repos: mint the ephemeral token before the backport action
and use it to author the backport PRs, then approve with GITHUB_TOKEN
(github-actions[bot]).

Because the PRs are authored by the ephemeral-token identity, github-actions[bot]
is a distinct identity and its approval counts toward the org "[org] Require a PR"
ruleset (verified against elastic/catalog-sync-config, which merges under the
identical org-only ruleset). Authoring with a real token also makes CI actually
run on the backport PRs. Falls back to GITHUB_TOKEN authoring (no auto-approval)
when the Token Policy is not yet registered.

Co-authored-by: Cursor <cursoragent@cursor.com>
…emeral-authored backports

Co-authored-by: Cursor <cursoragent@cursor.com>
@edsavage
edsavage force-pushed the feature/auto-approve-backports branch from 8535254 to bd2ca13 Compare July 24, 2026 02:03
@edsavage
edsavage marked this pull request as ready for review July 24, 2026 02:09
@edsavage
edsavage merged commit 621c993 into elastic:main Jul 24, 2026
8 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants