Skip to content

feat: open the metadata sync as a PR for review instead of merging it unattended - #464

Merged
twcclegg merged 1 commit into
mainfrom
feat/metadata-auto-merge-soak-window
Sep 16, 2026
Merged

twcclegg merged 1 commit into
mainfrom
feat/metadata-auto-merge-soak-window

Conversation

@twcclegg

@twcclegg twcclegg commented Sep 5, 2026

Copy link
Copy Markdown
Owner

The changelog-folding and commit-attribution half of this work is split out into #471. They touch adjacent regions of lib/github-actions-metadata-update.sh, so whichever merges second needs a rebase.

Problem

lib/github-actions-metadata-update.sh opened the metadata-update/* PR and enabled auto-merge in the same breath, so the 08:00 sync was usually merged, tagged, released and dispatched to nuget.org before anyone was awake. The only chance to look at one was to catch it during the few minutes its checks took.

Change

The sync opens the PR and stops. Reviewing and merging it is the intended way a metadata release ships.

Auto-merge is the backstop, not the plan. A later run that finds the PR still open regenerates the sync onto the same branch, force-pushes it, and turns auto-merge on — so a release is never left stalled because nobody was around to look. A PR opened at 08:00 that nobody touches merges shortly after 08:00 the following day.

Everything downstream is untouched: finalize_metadata_release.yml still tags the merge commit, creates the release and dispatches the publish. No workflow file changes at all.

Why the backstop regenerates rather than arming what is there

  • Auto-merge is armed at the one moment GitHub accepts it. enablePullRequestAutoMerge is rejected on a PR that could be merged right now (Pull request is in clean status) and only works while one is blocked — which the force-push has just made it. Arming a day-old PR isn't possible at all; merging it over the API instead would mean verifying the check rollup by hand, because libphonenumber-csharp-bot is an always-bypass actor on main's required_status_checks and the API would not refuse a red PR.
  • A commit pushed to the branch never reaches a release. metadata-update/* sits outside every ruleset, so the branch stays writable while the PR is open; the force-push overwrites whatever is there.
  • The checks gating the merge are minutes old, not a day stale against a main that has moved.

The trade-off, stated plainly: the commit that merges is not the one read the day before.

Deliberately not built

Any notion of declining a release. Closing the PR just means the next run opens another. A rejected sync either has to be skipped once and resumed at the next upstream release, or block releases indefinitely — neither is worth machinery, and upstream's next release is what actually resolves it. An earlier revision of this PR had --ignore-closed-pr and a closed-PR guard; both are gone.

Guards against a maintainer acting inside the minutes a run takes. Merging or closing by hand happens hours either side of a scheduled job, and upstream releases are at least five days apart, so at most one metadata PR is ever open. Re-reading the PR before the force-push, retrying the auto-merge arm against GitHub's async mergeability recompute, and rewriting the PR body mid-flight were all written and then removed on those grounds.

Skipping the .java/.proto gates on the backstop run. A .java change upstream means an actual port, and a run failing daily until someone does it is the existing, intended signal. If you ever dispatch with skip_java_check, the backstop won't fire and you merge the PR by hand.

A ref guard on the workflow. An earlier revision refused to run from any ref but main. It is not a security control — workflow_dispatch runs the workflow definition from the ref being dispatched, so the same push that edits lib/ can drop the guard — and keeping it meant special-casing dry_run to preserve the documented dry-run-from-a-branch path. This PR now leaves the workflow file untouched.

Not addressed here

Repo configuration rather than code:

  1. metadata-update/* is covered by no ruleset, so the branch is writable while the PR is open. Regenerating makes that harmless for the merge path, but restricting pushes to it would be better.
  2. README.md claims main requires every push to go through a PR "with no bypass for any actor (including this automation's own bot account)". Neither half is true. Corrected in feat: fold the changelog by authorship, and attribute the sync commit #471, which already rewrites that paragraph.
  3. finalize-metadata-release.sh derives its tag from the head ref with no check that it is newer than what is published.

@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.74%. Comparing base (af81b6d) to head (2ce3b16).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #464      +/-   ##
==========================================
+ Coverage   87.69%   87.74%   +0.05%     
==========================================
  Files          43       43              
  Lines        3893     3893              
  Branches      993      993              
==========================================
+ Hits         3414     3416       +2     
+ Misses        277      276       -1     
+ Partials      202      201       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@twcclegg twcclegg changed the title feat: hold metadata PRs open for 24h before auto-merging feat: hold metadata PRs open for an 18h review window before auto-merging Sep 5, 2026
@twcclegg
twcclegg force-pushed the feat/metadata-auto-merge-soak-window branch 4 times, most recently from be9fdbc to bda3893 Compare September 8, 2026 07:41
@twcclegg twcclegg changed the title feat: hold metadata PRs open for an 18h review window before auto-merging feat: give metadata syncs a review window by landing them on the next daily run Sep 9, 2026
@twcclegg
twcclegg force-pushed the feat/metadata-auto-merge-soak-window branch 3 times, most recently from 39d47ea to 1d03975 Compare September 10, 2026 14:14
@twcclegg
twcclegg force-pushed the feat/metadata-auto-merge-soak-window branch 2 times, most recently from 7a0056b to 966050a Compare September 12, 2026 23:27
@twcclegg twcclegg changed the title feat: give metadata syncs a review window by landing them on the next daily run feat: open the metadata sync as a PR for review instead of merging it unattended Sep 12, 2026
@twcclegg
twcclegg force-pushed the feat/metadata-auto-merge-soak-window branch 4 times, most recently from 6f4b13b to cfd1154 Compare September 13, 2026 17:23
… unattended

github-actions-metadata-update.sh opened the metadata-update/* PR and enabled
auto-merge in the same breath, so the 08:00 sync was usually merged, tagged,
released and dispatched to nuget.org before anyone was awake. The only chance to
look at one was to catch it during the few minutes its checks took.

The sync now opens the PR and stops. Reviewing and merging it is the intended
way a metadata release ships.

Auto-merge becomes the backstop rather than the plan: a later run that finds the
PR still open regenerates the sync onto the same branch, force-pushes it and
turns auto-merge on, so a release is never left stalled because nobody was
around to look. Regenerating rather than arming what is already there is what
keeps that backstop small - the commit that lands was built minutes earlier by
this script, so nothing has to reason about a day-old branch:

- Auto-merge is armed at the one moment github accepts it. The mutation is
  rejected on a PR that could be merged right now ("Pull request is in clean
  status") and only works while one is blocked, which the force-push has just
  made it. Arming a day-old PR is not possible at all; merging it over the api
  instead would mean verifying the check rollup by hand, since the bot account
  bypasses main's required status checks and the api would not refuse a red PR.
- metadata-update/* sits outside every ruleset, so the branch stays writable
  while the PR is open. The force-push overwrites whatever is there, so a commit
  pushed in the meantime never reaches a release.
- The checks gating the merge are minutes old rather than a day stale against a
  main that has moved.

Deliberately not built: any notion of declining a release. Closing the PR just
means the next run opens another. A rejected sync either has to be skipped once
and resumed at the next upstream release or block releases indefinitely, and
upstream's next release is the thing that actually resolves it.

Also not handled: a maintainer acting inside the minutes a run takes. Merging or
closing by hand happens hours either side of a scheduled job, and upstream
releases are at least five days apart, so at most one metadata PR is ever open.
@twcclegg
twcclegg force-pushed the feat/metadata-auto-merge-soak-window branch from cfd1154 to 2ce3b16 Compare September 14, 2026 01:37
@twcclegg
twcclegg merged commit 6448c5a into main Sep 16, 2026
6 checks passed
@twcclegg
twcclegg deleted the feat/metadata-auto-merge-soak-window branch September 16, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant