Prevent automated publishing conflicts by coordinating two separate things: which CI runs may overlap, and whether Git can safely advance the remote branch. A concurrency group can limit overlapping publishers that target the same branch or environment; Git’s fast-forward rule then protects remote history from a stale update. Neither safeguard replaces the other.
Why concurrent publishing runs conflict
GitHub Actions allows workflow and job runs to execute concurrently by default. If two publishers start from the same branch state, one may push first and advance the remote branch. The other publisher’s commit is then based on an older state, so Git may reject its push as a non-fast-forward update rather than overwrite the newer remote history. GitHub Actions concurrency documentation explains how runs can be grouped, while the Git push reference documents push behavior.
Coordinate runs that mutate the same target
In GitHub Actions, a concurrency group allows only one job or workflow in that group to run at a time. Give publishers that mutate the same target the same group key. Scope the key to the target: branch-based publishing can use a branch-specific key, while jobs deploying to a shared environment may need an environment-specific key. A key that differs across workflows will not coordinate them; a key that is too broad can unnecessarily serialize unrelated work. These outcomes follow from groups coordinating runs only when their keys match. See GitHub’s concurrency guidance.
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false
This illustrative configuration groups runs by triggering ref and does not cancel an in-progress run. It is not a guarantee of strict ordering, nor does it reconcile application-level conflicts. Check the current GitHub Actions workflow syntax reference when implementing configuration.
#1 Best Overall
Choose whether to cancel or queue pending work
Choose the policy based on whether every publication must be processed or only the latest generated state matters. Under the default pending-run behavior, a new pending run replaces the existing pending run in the same group. GitHub Actions also documents queue: max, which can hold up to 100 waiting workflow or job runs. GitHub warns that ordering is not guaranteed for ordinary concurrency groups, so do not rely on them for strict FIFO processing. See GitHub’s concurrency documentation.
- Only the newest output matters: consider canceling older work if the newer run can recreate the required final state. Cancellation can interrupt side effects, so confirm replacement is safe.
- Every publication must be processed: use a queueing approach and account for its capacity and ordering behavior. If strict sequence matters, provide an ordering design rather than assuming the concurrency group supplies one.
Recover safely from a non-fast-forward rejection
A rejection such as “non-fast-forward updates were rejected” means the publisher’s local branch is out of sync with, or behind, the upstream branch. Fetch the latest upstream state, integrate it with the intended publishing changes—or regenerate the output from current inputs—and then retry. The exact integration method depends on the repository’s publishing process. GitHub describes the out-of-date condition in its guide to non-fast-forward errors.
Rank #2
- Fetch the current upstream branch so the publisher can see the remote update.
- Integrate the publisher’s intended changes with that state, or regenerate the generated output from current inputs.
- Retry the push after the publisher is up to date.
Do not make force pushing the routine retry path. Git’s normal fast-forward restriction prevents replacing remote history with a branch that does not contain the remote tip; a force push overrides that protection and can discard a concurrent update. Use it only when replacing history is explicitly intended and the consequences are understood. See the git-push reference.
Use atomic pushes for multi-ref updates—not as a run lock
git push --atomic asks the server to update all refs included in that one push transaction or none of them. It works only when the remote supports atomic pushes. It does not serialize separate jobs, make separate remote connections atomic with one another, or replace a CI concurrency policy. See the Git push documentation.
Match the safeguard to the publishing requirement
| Requirement | Policy to consider | Important limit |
|---|---|---|
| Only the newest generated publication matters | Share a concurrency group; consider canceling in-progress work when safe. | Cancellation can interrupt side effects. Confirm a newer run can recreate the required final state. |
| Every publication must be processed | Use a shared concurrency group with queueing. | queue: max allows up to 100 waiting runs according to GitHub Docs; ordinary concurrency ordering is not guaranteed. |
| Several refs must update together | Use git push --atomic if the server supports it. |
Atomicity covers refs in that push transaction, not separate jobs or remotes. |
| A push is rejected as non-fast-forward | Fetch, reconcile or regenerate, then retry. | Force pushing can replace newer remote history. |
The right policy depends on whether publishing is replaceable or every run carries required work; the platform and Git behaviors above do not prescribe one universal workflow.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




