All posts

The tag you reviewed is not the code that runs: GitHub Actions lockfiles

GitHub · 4 min read ·

You review a pull request that adds uses: some-org/deploy-action@v4 to a workflow. The action looks fine, you approve it, and it runs. A month later the same workflow runs again, still pointing at v4, and executes code nobody on your team has read. Nothing changed in your repository. The tag moved. GitHub put workflow lockfiles into public preview on 14 October 2026 to close exactly that gap, and it is available on Free and Team plans as well as Enterprise Cloud.

review day run day tag v4 commit A you read this tag v4 commit B nobody read this same workflow file your repository did not change, the tag did
A tag is a pointer someone else controls. Your review approved what it pointed at that day.

Why a reviewed workflow is not a reviewed run

Tags and branches can be moved after you review a workflow. GitHub's own description of the risk is blunt: if an action is compromised, a moved tag or branch can bring malicious code into your runs with no change in your repository at all. Your diff history stays clean, your branch protection never fires, and the only place the change exists is in someone else's repository. For a workflow that holds a deploy key or a registry token, that is the whole game.

What the lockfile pins

According to GitHub, the lockfile lets you keep readable references like @v4 in the workflow while Actions runs the exact commits you locked. You generate it with the gh actions-lock extension for the GitHub CLI, which writes .github/workflows/actions.lock containing reviewable commits for your actions, their transitive dependencies, and the dependencies of reusable workflows you call. If the tag moves later, GitHub says the run still uses the recorded commit.

workflow says uses ...@v4 actions.lock action: commit A its dependency: commit C run uses A and C tag v4 moved to commit B ignored until you regenerate the lock
The reference stays readable for people. The commit that runs is the one written down in the lockfile.

How this differs from pinning SHAs by hand

The long-standing manual fix, as general practice, is to write the full 40-character commit SHA in the uses: line. It works, but it is hard to read, easy to forget to update, and it only pins the action you named. If that action is a composite action that calls other actions by tag, those inner references are still free to move. The lockfile covers the transitive layer, and with Dependabot GitHub says a version update for an action in an onboarded workflow regenerates the matching lockfile entry in the same pull request, so the pin and the upgrade are reviewed together.

Adopting it without a big-bang change

GitHub's extension lets you onboard workflows one at a time. Once a workflow is onboarded it must have lockfile entries for its dependencies, and runs use the recorded commits. The same extension can check for a stale lockfile without modifying files, which fits a CI step that fails the pull request when the lock is out of date. Admins at repository, organisation or enterprise level can also turn on a "Require lockfile" policy, also in public preview.

A lockfile makes the code you run stay the code you reviewed. It does not make that code safe: GitHub says so itself, so review an action before you lock it.

A practical next step: pick the one workflow in your repository that holds the most sensitive secret, usually the deploy, run gh actions-lock on it, read the commits it records, and add the stale-lockfile check to CI. Then move to the next workflow. GitHub credits Andrew Nesbitt, François Proulx, Jesse Houwing and William Woodruff for working with it on the feature. Source: GitHub Changelog, workflow lockfiles in public preview.

Notes from readers

  1. No notes yet. The first one is the bravest.
Building something like this? I can help. Message me on WhatsApp