diff --git a/.github/workflows/release-please-reusable.yml b/.github/workflows/release-please-reusable.yml index a3da862..3fc4423 100644 --- a/.github/workflows/release-please-reusable.yml +++ b/.github/workflows/release-please-reusable.yml @@ -116,6 +116,22 @@ jobs: permissions: contents: write pull-requests: write + # Serialize runs within the caller's repo. GitHub occasionally delivers + # one push as two workflow runs; unserialized, both build the same release + # commit and the slower one fails updating the release branch ref. The + # group is repo-wide, not per-ref, because release-please always targets + # the default branch. GitHub keeps one pending run per group and cancels + # it when a newer run queues. That loses no release work, because + # release-please reads the branch's current HEAD; `queue: max` would avoid + # the canceled run once actionlint accepts the key (#40). A caller that + # declares a group of this name deadlocks with this job. + concurrency: + group: release-please-reusable + cancel-in-progress: false + # Bound a hung run: it holds the concurrency group, so no later run in + # the repo can start until it ends. The slowest caller's job takes just + # under five minutes. + timeout-minutes: 30 # Hoist credentials to job env so step-level `if:` can read them — GitHub # allows the `env` context in step `if:` only when env is defined at the # workflow or job level, and the `secrets` context isn't allowed in `if:` diff --git a/README.md b/README.md index ebee68d..71a9abc 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ jobs: Treat `body`, `pr`, `prs` and `name` as untrusted text: they come from commit messages and PR titles. Pass them to a script through `env:` — never interpolate them directly into a `run:` block. +The job runs in a repo-wide `release-please-reusable` concurrency group without `cancel-in-progress`, so overlapping pushes (or a single push GitHub delivers twice) wait instead of racing to update the release PR branch. GitHub keeps one pending run per group, so a third overlapping run cancels the one already waiting. The canceled run shows as a failed check on its commit, but no release work is lost: the replacement reads the branch's current HEAD, not the commit that triggered it. [#40](https://github.com/openCoreEMR/github-workflows-public/issues/40) tracks removing that case with `queue: max`. The job times out after 30 minutes so a hung run cannot hold the group indefinitely. Don't declare a concurrency group of the same name in the caller; caller and callee would deadlock on it. + Inputs: | Name | Type | Default | Description |