GitHub Actions Lockfiles Pin Workflow Dependencies by Commit
GitHub put GitHub Actions lockfiles into public preview today. A new file, .github/workflows/actions.lock, records the exact commit behind every action, transitive dependency and reusable workflow your pipelines call, so a moved tag can no longer change what runs. This piece covers what shipped, what it does not do, and how to onboard a repo this week.
What GitHub Actions lockfiles record
Tags and branches move. If someone compromises an action and repoints v4, your next run executes new code, and nothing changed in your repo. That is the attack shape behind the March 2025 tj-actions/changed-files incident, where version tags were moved to a malicious commit.
Per GitHub's announcement, the lockfile ties runs to the commits you reviewed. The gh actions-lock GitHub CLI extension generates it, and you should never edit it by hand. A workflows section lists what each onboarded workflow uses. A dependencies section maps each reference to its ref, commit, owner_id and repo_id. At run time, Actions uses the two IDs to confirm the repository behind a name is still the one you locked.
This is an abridged sketch based on the lockfile docs. The real file has more entries, so check the docs for the full layout.
# This file is machine-generated by `gh actions-lock`.
version: 'v0.0.2'
workflows:
'.github/workflows/ci.yml':
- 'octo-org/deploy-action@v3.1.0'
dependencies:
'octo-org/deploy-action@v3.1.0':
ref: 'v3.1.0'
commit: 'sha1-...'
owner_id: 44036562
repo_id: 197814629
The side benefit: the docs say repos that adopt a lockfile no longer need SHA pinning in the workflow files. Your YAML keeps readable versions and the lockfile holds the hashes.
Why this matters if you ship AI
Your deploy pipelines hold registry pushes, cloud roles and model API keys. A build step that silently changes is a direct path to all of them.
Locking is not vetting, though. It does not judge whether a dependency is safe. It only guarantees that the code you reviewed is the code that runs.
Onboard one workflow with gh actions-lock
Start with a single workflow. Passing a path onboards just that file, and running with no arguments scans every workflow under .github/workflows/. The docs recommend enabling Dependabot before you create the lockfile.
gh extension install github/gh-actions-lock
gh actions-lock .github/workflows/ci.yml
git diff
The extension also rewrites references, for example octo-org/deploy-action@v3 becomes @v3.1.0, and local uses: ./ references become uses: $/. Read the whole diff, then commit the lockfile and the workflow changes together. A changed commit is code that will run, so review it like a dependency bump. A changed owner_id or repo_id should be rare, so investigate before merging.
When a tag legitimately moves, re-resolve it:
gh actions-lock --relock
# Only if the locked commit is unreachable upstream, and you have investigated why:
gh actions-lock --relock --accept-moved
Verify the lockfile in CI
gh actions-lock --verify checks the lockfile without writing anything and exits with an error if it is stale. Run it on pull requests and make it a required check. This is a minimal sketch. The docs don't state token requirements for --verify, so the GH_TOKEN wiring is my assumption. GitHub's enforcement guide points to the gh-actions-lock repo for fuller versions.
name: actions-lock
on:
pull_request:
paths:
- '.github/workflows/**'
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install the extension
run: gh extension install github/gh-actions-lock
env:
GH_TOKEN: ${{ github.token }}
- name: Fail if actions.lock is stale
run: gh actions-lock --verify
env:
GH_TOKEN: ${{ github.token }}
Onboard this workflow too, since it has dependencies of its own. For a fast local check, gh actions-lock --verify-local --no-fix works offline as a pre-commit hook. It only confirms every reference has an entry and does not re-verify pins against GitHub.
Keep it current with Dependabot, enforce it with Require lockfile
For onboarded workflows, a Dependabot action update now regenerates the matching lockfile entry in the same pull request. Dependabot never onboards a workflow, so it maintains coverage without extending it.
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
Then add a code owner so lockfile changes get a human:
# .github/CODEOWNERS
.github/workflows/actions.lock @<your-org>/<platform-team>
The Require lockfile policy, also in preview, blocks runs of targeted workflows that have action dependencies but are not onboarded. It can be set at enterprise, organization or repository level. Pilot it in Evaluate mode (GitHub Enterprise only) and read Policy insights. Switch to Active once the targeted repos and workflows are onboarded. The enforcement guide is explicit that the policy blocks runs but does not keep lockfiles fresh, so keep the CI check.
Gotchas
- It is a public preview and subject to change, on Enterprise Cloud (including data residency), Team and Free plans.
- Allowlists: tag entries like
actions/checkout@v6no longer match locked SHAs. Use a wildcard or the full commit SHA. - A reusable workflow is locked by the lockfile in the repo where it is defined, so onboard shared workflow repos first.
- Branch and partial version references like
mainorv3are not re-resolved on a normal run. Use--relock. - The lockfile covers only workflows in its own repository.
What to do on Monday morning
- Enable Dependabot for the
github-actionsecosystem. - Onboard your least scary workflow and review the full diff.
- Add the
--verifycheck and a CODEOWNERS line. - Turn on Require lockfile in Evaluate mode and read the insights before going Active.
Your AI pipelines hold the keys to your models and data, so knowing exactly which commits execute inside them is a production requirement.