Find the race that ships an older commit

A deploy that races itself does not fail. It succeeds — twice, in the wrong order — and what is left behind is a green check on the newer commit sitting above a live site running the older one. Paste a GitHub Actions workflow below and this page names every way yours is permitted to do that, with the line and the fix. Free, no sign-in, and nothing you paste leaves your device.

Paste one workflow at a time. Nothing is uploaded — the scan runs in this page.

Findings

Nothing scanned yet. Paste a workflow above, or load the example to see what a report looks like.

The five things it looks for

  1. No concurrency group on a deploying job. Two pushes run at once and the one that finishes last is the one that is live.
  2. A group that changes every run — keyed on github.sha, github.run_id and friends. Every run gets its own group, so nothing waits for anything. The dangerous one, because the block is there and reads as protection.
  3. cancel-in-progress on a deploy. Right for tests, wrong for a deploy: a cancelled deploy has already written part of its change and nothing re-runs to finish it.
  4. Split groups on one environment. Two jobs, or two workflows, writing to the same target under different group names do not serialize against each other at all.
  5. A deploying job that checks out a moving branch. A branch name resolves when the step runs, so a queued or re-run job can ship a different tree than the one it was queued for.

Read the longer version, with what each failure looks like from the outside, in how a deploy ships an older commit. There is also a comparison against reading the workflows yourself.

Run it in your own CI

The same logic ships as check.py — Python standard library only, no dependencies, no network access, and no GitHub token. A test in this repository runs both over the same workflows and asserts they report the same findings, so the CLI and this page cannot drift apart.

python3 check.py .github/workflows/          # scan them all
python3 check.py .github/workflows/ --fail-on-risk   # exit 1 on a high-risk finding
python3 check.py deploy.yml --json          # stable ids, for your own tooling

Questions

What is a deployment race?

Two runs of the same deploy workflow overlapping in time. Push commit A, then commit B a minute later, and both runs start. If A's run happens to finish last — because it queued for a runner, because its build was slower, because a step retried — then A is what is live, while B's green check sits at the top of the branch. Nothing failed, and the run log for A does not say it overwrote anything, because from inside that run nothing did.

Does a concurrency block always fix it?

Only if the group name is the same for every run that writes to the same target. A group keyed on something that changes each run puts every run in a group of its own, so nothing ever waits for anything. That is the same race as having no block at all, with the difference that the block is there and reads as protection, which is why it survives review. Two workflows deploying the same environment under two different group names have the same problem for the same reason.

Should a deploy use cancel-in-progress?

Usually not. Cancelling is right for tests, where the only cost of stopping a superseded run is the time it had used. A deploy cancelled part-way through has already written part of its change — some of a batch of uploaded files, half a copied artifact, a migration that ran without the code expecting it — and nothing re-runs to finish it. For a deploy, queueing the newer run instead means it applies last, which is the order you want.

Is anything I paste uploaded?

No. The scan runs in this page's own JavaScript, on your device. Nothing you paste is sent anywhere, stored on a server, or logged. This page makes no network request of any kind after it loads — no analytics service, no font, no CDN. The same logic ships as a stdlib-only Python script with no network access at all, if you would rather not paste a workflow into a web page.

Can I run this in my own CI?

Yes. The CLI takes workflow files or a directory of them, prints the same findings this page shows, and can exit non-zero when a high-risk finding is present, so it can gate a pull request. It is free, MIT-licensed, has no dependencies beyond the Python standard library, and never calls the GitHub API.