You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Standing-PR workflows: an update can run during a publish and double-bump the next release #790
#753 (for #739) gives a merged PR's publish runs a concurrency group of their own, so a newer push, schedule or label run can't replace a queued publish. The cost is that publishes and updates no longer wait for each other.
An update can double-bump. A feeder PR that merges during a standing-PR publish starts an update before the release's tags exist. The update's mid-run guard (standing-pr.ts, "a release commit landed on the base branch during this run") only bows out when the release commit is HEAD. With the feeder on top, the update recomputes from the last tag plus the release commit's bumped package.json, which the guard's own comment says double-bumps.
An update that finishes after that reconcile still leaves the wrong standing PR in place until the next update, which can be up to an hour away (the hourly schedule). Merging it in that window ships the double bump.
Immediate releases overlap too.immediate-release runs on the feeder PR's own closed event, in that PR's own group, so it can run alongside a standing-PR publish or an update.
An update's push can fail. An update whose checkout saw release/next before a publish deleted it fails its --force-with-lease push, because the remote-tracking ref is stale. No new standing PR appears until the next run.
Bail out on any untagged release. Make the update bow out whenever an untagged release commit sits between the last release tag and HEAD, not only at HEAD. A failed publish also leaves its release untagged, and roll-forward says the next release supersedes a failed one, so this must not stop updates for good.
Start the publish from the push run. Publish from the push run of the release merge, in the same group as updates, as this repo's own .github/workflows/standing-pr.yml does (detect-release-merge). pull_request events keep a group of their own. This is a larger template change.
Prune before pushing. Independently of 1 or 2, have the update fetch with --prune before its --force-with-lease push, so a branch the publish deleted doesn't count as stale.
A reconcile step in the publish jobs doesn't fix this. I tried adding refresh-after-release to the publish jobs and took it out again. The update checks for releasable changes on the job's own checkout before it fetches main (runStandingPRUpdate: the dry run comes before resetReleaseBranch). In a publish job, that checkout is the tagged merge commit, so the reconcile found nothing to release and closed the standing PR a concurrent update had just opened. Any fix that reconciles after a publish has to analyse the latest main.
Events that run no job can replace a queued update. Open-PR events like a label on a feeder PR, or a bot's body edit with an App token, still go to the shared group. A fix would route every event that runs no job to a group of its own.
The preview refresh runs twice.publishFromManifest and runRelease already refresh feeder previews in-process, so a refresh-after-release step after them repeats the work when ci.prPreview.refreshAfterRelease is on.
A standing-PR publish can run alongside an immediate release. The publish now has a per-PR group, while a feeder's release:immediate merge stays in the shared group with the updates. If both run at once, releasekit release can compute the same version again before the publish has tagged, and both push to main, so one is rejected or the npm publish is skipped while its tag points elsewhere. That's the same trade-off as the update overlap above.
A queued immediate release can still be dropped. In the shared group, a newer push, schedule, label or bot-edit run replaces it while it's pending.
Checkbox-edit updates don't queue with an immediate release. They run in the per-PR edit group, so one can rewrite release/next and the manifest at the same time as the immediate release's refresh-after-release.
Hotfix branches land in the wrong group. The expression treats any release/* head as the standing PR, so a feeder PR from a branch like release/1.x goes to a per-PR group.
One design would fix most of this. Move publish and retry into their own workflow file with a simple per-PR group, instead of routing everything through one long expression copied into three files.
A design option from the last review of #753, possibly the simplest fix for the routing problems here: put concurrency on the jobs instead of the whole workflow.
A skipped job never joins a concurrency group, so runs that do nothing (label changes on feeder PRs, a bot's body edits, other events on a closed PR) couldn't replace a queued update or release. Each job could then use a plain group of its own: the update and immediate release in one shared group, and the standing PR's publish and retry in a per-PR group. That removes the long expression copied into the template, the ci-setup snippet and the example, along with its noop and per-PR special cases.
It doesn't solve the publish-vs-update overlap on its own. A queued publish still mustn't be replaceable, so publish still can't share a group with updates.
Put concurrency on the jobs instead of the workflow, so runs that do nothing can't replace queued ones.
Fetch with --prune before the update's --force-with-lease push.
Option 1. The update bails while a release commit is untagged and no failure report exists for it, with a time limit as a backstop so a crashed publish can't stall updates for good. The publish job then ends with a reconcile that analyses the latest main, not the job's own checkout.
Every job that writes (publish, immediate release, update) runs with main as its ref, in one queue.
Label and checkbox events on the open standing PR go through a small relay that dispatches the update on main. The relay needs only permission to start a workflow, not secrets.
An environment restricted to main then holds the registry credentials, plus a GitHub App key used as the ruleset bypass instead of the Actions bot.
Release detection keys on an untagged release commit, not on whether the latest commit is the release commit. That way a queued run that a newer push replaces still gets its publish.
Why 2 as well as 1: option 1 fixes an update building on an untagged release. On its own it can't make an immediate release merged during a standing-PR publish wait its turn, and it can't stop a queued immediate release being dropped. Option 1 stays as the safety net for runs outside the queue, including consumers on older workflows.
Activity
goosewobbler commentedon Oct 24, 2026
Notes from the second review of #753:
refresh-after-releaseto the publish jobs and took it out again. The update checks for releasable changes on the job's own checkout before it fetchesmain(runStandingPRUpdate: the dry run comes beforeresetReleaseBranch). In a publish job, that checkout is the tagged merge commit, so the reconcile found nothing to release and closed the standing PR a concurrent update had just opened. Any fix that reconciles after a publish has to analyse the latestmain.immediate-releasehas the same stale-checkout problem. Itsrefresh-after-releaseruns on the feeder PR's merge commit, so commits that landed after it aren't seen. That was already true before fix(ci): standing-PR templates — publish concurrency, LLM on publish, pnpm setup #753.release:immediatePR in the shared group, so it doesn't race the update from its own merge. A newer push, schedule or label run can still replace it while it's pending, as before fix(ci): standing-PR templates — publish concurrency, LLM on publish, pnpm setup #753.publishFromManifestandrunReleasealready refresh feeder previews in-process, so arefresh-after-releasestep after them repeats the work whenci.prPreview.refreshAfterReleaseis on.goosewobbler commentedon Oct 24, 2026
More from the final review of #753:
release:immediatemerge stays in the shared group with the updates. If both run at once,releasekit releasecan compute the same version again before the publish has tagged, and both push tomain, so one is rejected or the npm publish is skipped while its tag points elsewhere. That's the same trade-off as the update overlap above.editgroup, so one can rewriterelease/nextand the manifest at the same time as the immediate release'srefresh-after-release.release/*head as the standing PR, so a feeder PR from a branch likerelease/1.xgoes to a per-PR group.goosewobbler commentedon Oct 24, 2026
A design option from the last review of #753, possibly the simplest fix for the routing problems here: put
concurrencyon the jobs instead of the whole workflow.A skipped job never joins a concurrency group, so runs that do nothing (label changes on feeder PRs, a bot's body edits, other events on a closed PR) couldn't replace a queued update or release. Each job could then use a plain group of its own: the update and immediate release in one shared group, and the standing PR's publish and retry in a per-PR group. That removes the long expression copied into the template, the ci-setup snippet and the example, along with its noop and per-PR special cases.
It doesn't solve the publish-vs-update overlap on its own. A queued publish still mustn't be replaceable, so publish still can't share a group with updates.
goosewobbler commentedon Oct 24, 2026
Decision (maintainer): do all four options, in this order.
examples/cithe only workflow source, so each change below lands in one copy.concurrencyon the jobs instead of the workflow, so runs that do nothing can't replace queued ones.--prunebefore the update's--force-with-leasepush.main, not the job's own checkout.id-token: writereach every workflow #785's option C.mainas its ref, in one queue.main. The relay needs only permission to start a workflow, not secrets.mainthen holds the registry credentials, plus a GitHub App key used as the ruleset bypass instead of the Actions bot.Why 2 as well as 1: option 1 fixes an update building on an untagged release. On its own it can't make an immediate release merged during a standing-PR publish wait its turn, and it can't stop a queued immediate release being dropped. Option 1 stays as the safety net for runs outside the queue, including consumers on older workflows.
goosewobbler commentedon Oct 24, 2026
Split into three slices, in order:
main(after fix(release): fail standing-pr update without GitHub context #794)mainin one queue, behind an environment-guarded App identity, absorbing Standing-PR setup: bypasses for the Actions bot and workflow-wideid-token: writereach every workflow #785's option C (after Docs: make examples/ci the only workflow source and delete templates/workflows #766, Standing-PR workflows: put concurrency on jobs, and prune before the update pushes #798 and standing-pr update: bail while a release is untagged, and reconcile from the latest main #799)