Skip to content

ci: Bulk Migration — replay bulk migrations onto an open PR - #8662

Open
keithharvey wants to merge 1 commit into
beyond-all-reason:masterfrom
keithharvey:gh-migrate-action-env
Open

ci: Bulk Migration — replay bulk migrations onto an open PR#8662
keithharvey wants to merge 1 commit into
beyond-all-reason:masterfrom
keithharvey:gh-migrate-action-env

Conversation

@keithharvey

@keithharvey keithharvey commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Part of #7408.

A repo-wide mechanical change rewrites lines that in-flight branches also touch, so every open PR conflicts with master until it is replayed through the same transforms. The transforms are deterministic and idempotent — run them on both sides and the mechanical conflicts cancel out, leaving only genuine ones.

Contributors can already do that themselves in three lines. This adds the other half: a maintainer runs this workflow on a PR and the same three lines run in CI, on the same BAR-Devtools code path, and the result is pushed back.

just bar::migrate::<name>
git commit -am 'apply <name> migration'
git merge origin/master

Nobody has to install anything to stay current, and nobody gets marooned on a branch that can no longer be merged.

Usage

From the Actions tab, or:

gh workflow run bulk_migration.yml --repo beyond-all-reason/Beyond-All-Reason \
  -f pr=<number>

By default it replays every migration BAR-Devtools still lists, oldest first, so a branch that predates several catches up on all of them in one pass — migrations the branch already has produce nothing and are skipped. The optional migration input narrows it to one:

gh workflow run bulk_migration.yml --repo beyond-all-reason/Beyond-All-Reason \
  -f pr=<number> -f migration=stylua-cleanup

Dispatch is the whole trigger surface, and that is the intended shape for now. A /migrate PR-comment trigger is the obvious next step, and deliberately not in this PR: it would fire on all ~155 comments/week this repo gets, and it is easy to add once someone actually wants it.

Trying it before the dependencies land

Three further inputs — devtools_repo, devtools_ref, image — default to the canonical values and exist so the whole path can be exercised against a fork first. Normal use leaves them alone. Pointed at the fork this was verified on — both images are public and the workflow is on that fork's master, so this runs as-is:

gh workflow run bulk_migration.yml --repo keithharvey/bar --ref master \
  -f pr=<number> \
  -f devtools_repo=keithharvey/BAR-Devtools \
  -f devtools_ref=bar-fmt-03-llm \
  -f image=ghcr.io/keithharvey/bar-migration:latest

Two things to know if you do. workflow_dispatch only registers a workflow that is on the repository's default branch, so this file has to be on your fork's master before it can be dispatched at all — being manual-trigger only, it is inert sitting there. And devtools_ref can be the working branch: the images live in GHCR independently of any branch, so publishing them does not commit you to keeping the recipes on master.

Once this is on master but BAR-Devtools#57 is not, dispatch moves here — real PR numbers, no --ref — and only the BAR-Devtools half stays overridden:

gh workflow run bulk_migration.yml --repo beyond-all-reason/Beyond-All-Reason \
  -f pr=<number> \
  -f devtools_repo=keithharvey/BAR-Devtools \
  -f devtools_ref=bar-fmt-03-llm \
  -f image=ghcr.io/keithharvey/bar-migration:latest

The migration list is not maintained here

It is read from BAR-Devtools at run time:

just --unsorted --list bar::migrate

just/bar-migrate.just already declares migrations in adoption order and retires them by marking them [private], which drops them from --list. So that command is "every migration still worth replaying, oldest first" — there is no second copy of the list in this repo to drift out of step.

This also settles the security question. A name from a PR comment is only ever compared against that list, never passed through, so a [private] recipe such as stylua-cleanup-generate (which would regenerate the whole branch stack) cannot be reached from a comment.

When the merge conflicts

It bails without pushing. Both sides have been through the same deterministic transforms at that point, so whatever still conflicts is a real disagreement with master — not something a replay can settle. The job fails, and the PR comment lists the conflicted paths.

Pushing to forks

GITHUB_TOKEN is scoped to this repository and cannot push to a fork, even when the PR has Allow edits by maintainers enabled. So:

PR head What happens
Same repo Pushed straight to the PR branch.
Fork Parked on migrate/pr-<N> here; the comment gives the author a one-line git pull.

No secrets, nothing to provision. A PAT could make the fork case push directly too, but that needs both the token and the PR's Allow edits by maintainers box, and silently falls back to the second row when either is missing — not worth the branch until the fork friction actually proves painful.

The toolchain is not restated here either

The job runs in ghcr.io/beyond-all-reason/bar-migration:latest — BAR-Devtools' own dev image, built from the docker/dev.Containerfile that is already the canonical dependency list for local development, plus a prebuilt copy of the codemod binary.

This matters more than it looks. StyLua's output is version-specific: a workflow that assembled its own toolchain would eventually format lines master's own run did not, and that is precisely when the mechanical conflicts stop cancelling out and the whole premise fails. Sharing the image makes that impossible rather than merely unlikely — there is no version number in this file to drift.

Recipes are not baked into the image; they are checked out at devtools_ref on every run. So a change to bar::migrate::* takes effect on the next dispatch with no image rebuild, while the codemod binary — which is baked — is rebuilt and republished automatically when its source changes.

Both images are published by a new dev-image workflow on the BAR-Devtools side. They must be public for this job to pull them without credentials.

Verified

Replayed end to end onto a throwaway PR, in that image, with no install steps: 8,648 bracket-to-dot conversions, StyLua across the tree, one commit pushed to the PR head, and the summary comment posted. Run against fork-published copies of both images, via the override inputs above.

The image's prebuilt codemod is picked up rather than rebuilt. CODEMOD_BIN short-circuits require-codemod, so the ~2.5 min cargo build an earlier revision of this workflow paid on every run does not happen — confirmed by the absence of any cargo output in the run log, with the three transforms starting about a second apart.

Also checked: every tool the job needs is present in the image (gh, jq, just, cargo, stylua 2.0.2, lx 0.28.3, lua, git), and the recipes do not try to re-exec themselves into a distrobox when _DEVTOOLS_IN_DISTROBOX is set.

Merge ordering

Independent of the type-cleanup stack and safe to merge first — which is the point, since it is the answer to "what happens to in-flight branches when the reformat lands".

It needs BAR-Devtools#57 on BAR-Devtools master for both the bar::migrate recipes and the published images, and it needs a migration on this repo's master to have something to replay. It is manual-trigger only, so merging it before either of those is inert rather than wrong.

LLM Disclosure

Opus 5 on this one. It did a far better job on the description than I ever would've done after I slogged through the action myself, so I largely left it after some tweaks.

@keithharvey
keithharvey force-pushed the gh-migrate-action-env branch 3 times, most recently from 390a156 to d735bb8 Compare August 6, 2026 21:08
@keithharvey keithharvey changed the title ci: /migrate — replay bulk migrations onto an open PR on request ci: Bulk Migration — replay bulk migrations onto an open PR Aug 6, 2026
@keithharvey
keithharvey force-pushed the gh-migrate-action-env branch from d735bb8 to cbc1de6 Compare August 6, 2026 21:13
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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