Originally published on DevToolHub.
GitHub Actions workflow lockfiles went into public preview on October 14, 2026 and became generally available on October 28. The timing is almost funny: GitHub automated the exact workaround this blog told you to do by hand. A GitHub Actions lockfile pins every action in a workflow, plus its transitive dependencies, to a commit you've actually reviewed, so a tag moving after merge can't quietly swap in different code. Here's what .github/workflows/actions.lock locks, how to roll it out without breaking CI, and the limits GitHub documents but the announcements skip.
I haven't run the extension against a real repository for this guide. Everything below comes from GitHub's changelog, GitHub Docs and the gh-actions-lock README as of November 3, 2026, when the extension was at v0.4.1. The extension is young and its flags have already changed between releases, so treat gh actions-lock --help as the final word.
The Problem: Tags and Branches Move After You Review Them
When you review a pull request that adds uses: actions/checkout@v4, you're reviewing whatever commit v4 points to at that moment. Tags are mutable refs. A maintainer, or someone who compromises a maintainer's account, can repoint v4 tomorrow, and your workflow picks up the new code on its next run. No new pull request, no re-review.
That's not hypothetical. Several real GitHub Actions supply-chain incidents followed exactly this pattern: compromise the account behind a popular action, repoint the tag, then wait for CI everywhere to pull the new commit. Manual SHA pinning closes this gap one action at a time, but doing it by hand across dozens of workflows and their transitive dependencies doesn't scale, and nobody relocks a hand-pinned ref once it goes stale.
What a GitHub Actions Lockfile Actually Pins
A workflow lockfile lives at .github/workflows/actions.lock. It's one file per repository, with entries for every workflow you've onboarded, and it's machine-generated: GitHub says never to edit it by hand. It pins a commit SHA to every action your workflows depend on, including transitive dependencies, meaning the actions your actions call internally.
Each locked dependency records four things: the ref it was resolved from (a tag or branch), the exact commit that ref pointed to, and the numeric owner_id and repo_id of the source repository. At run time, GitHub Actions uses those two IDs to confirm that the repository behind the name is still the one you locked.
Once a workflow is onboarded, every action it uses must be in the lockfile, and the locked commit is what runs. That commit also has to be reachable from the recorded branch or tag in the action's own repository. A commit that exists only in a fork, or that's no longer reachable, is rejected when the workflow runs.
One boundary catches people out: a lockfile doesn't pin reusable workflow calls. GitHub's docs are explicit that jobs.<id>.uses calls aren't covered, and that to pin a reusable workflow you reference it by its full commit SHA. The dependencies of a reusable workflow are covered by the lockfile in the repository where that workflow is defined, not by yours. So if you call shared workflows from another repository, that repository needs its own lockfile.
Two more details. A lockfile only controls which action code runs. It doesn't control input parameters, secrets or the runner image. And there are two ways to refer to an action in your own repository: uses: ./path runs whatever is checked out in the runner's workspace, so the extension can't pin it, while uses: $/path runs your repository's action at the workflow's own commit, so it's pinned and the actions it uses can be locked.
Installing and Generating Your First actions.lock
Install the gh-actions-lock extension through the GitHub CLI. GitHub recommends turning on Dependabot for GitHub Actions first, so the lockfile stays in sync from day one.
gh extension install github/gh-actions-lock
From the root of your repository, run it with no arguments. It scans every workflow under .github/workflows/, pins each resolvable action to a commit SHA, and writes the lockfile.
gh actions-lock
To onboard a single workflow instead of the whole directory, pass its path.
gh actions-lock .github/workflows/ci.yml
Check your uses: lines before you run it. The lockfile pins whatever each ref resolves to right now, so make sure those are the versions you actually want locked.
Review the whole diff, not just the lockfile. The extension also rewrites your uses: references to the specific version it resolved, so octo-org/deploy-action@v3 can become octo-org/deploy-action@v3.1.0. That makes the reference more specific without changing the commit it points to. If you'd rather keep partial versions like v4 as written, pass --no-narrow.
Local actions need a decision. The extension never follows or locks a ./ local action. If a local action in your checkout uses other actions, the run reports a local-action error and writes nothing. You have two options:
gh actions-lock --migrate-local-actions
gh actions-lock --ignore-local-actions
--migrate-local-actions rewrites same-repository ./… references to $/… so the action runs as a pinned reference. It only happens when you pass the flag, because the two forms behave differently. --ignore-local-actions keeps the ./ path and downgrades the error to a warning, but a local action that uses an unlocked action will then fail at run time, partway through the job. GitHub recommends the migrate option.
GitHub Enterprise Server isn't supported. The extension works with github.com and GitHub Enterprise Cloud, including data-residency tenants (*.ghe.com). For a tenant, run gh auth login --hostname TENANT.ghe.com and run the tool from the tenant repository checkout. Public actions like actions/checkout are still read from github.com, so in CI on a tenant you'll want a github.com token with no permissions in GH_ACTIONS_LOCK_GITHUB_COM_TOKEN. Without one, those lookups are anonymous and share GitHub's limit of 60 requests an hour per IP address.
Rolling Out a GitHub Actions Lockfile One Workflow at a Time
You don't have to lock every workflow in one pull request. A scoped run touches only the workflow you name, so you can onboard your highest-risk pipeline first: the one that runs on every pull request, or the one holding deploy credentials. Only a full-directory run prunes entries for deleted workflows, so run it without a path now and then to keep the file clean. Anyone with write access to the repository can do the onboarding.
Expect a real diff the first time, and check one thing before you merge: your allowed-actions policy. After a workflow is onboarded, the allowlist is checked against locked commit SHAs instead of tags. An entry pinned to a tag, such as actions/checkout@v6, no longer matches. Use a wildcard or the full commit SHA instead, or onboarded workflows will start failing.
After onboarding, rerun the tool whenever you add a workflow that has uses: dependencies, or add or remove a uses: dependency in an existing one.
Keeping Your GitHub Actions Lockfile Current
Locked pins are sticky by design, and what happens when a ref moves depends on the ref:
-
A branch or partial version such as
mainorv3keeps its locked commit, and the extension reports a ref-moved warning. Check the upstream history, then rungh actions-lock --relockto advance it. -
A full version tag such as
v3.1.0must point at its locked commit. If the tag moved, you get an error until you run--relockor change theuses:line. -
Rewritten history, where the locked commit is no longer in the reference's history (a force-push, for example), is an error that
--relockdoesn't fix. After you've reviewed the upstream history, rungh actions-lock --accept-moved. GitHub says not to use--accept-movedin automation.
Dependabot handles the routine bumps. When it opens a version update for an action in an onboarded workflow, it regenerates the matching lockfile entry in the same pull request, with no extra configuration, so the workflow file and the lockfile can't drift apart. It only maintains coverage, though. It won't onboard a workflow that has no lockfile entries yet.
Treat lockfile changes like any other dependency change. A changed commit is new code your workflows will run. A changed owner_id or repo_id should be rare and means the repository behind that name isn't the one you originally locked, so investigate before you merge. Adding .github/workflows/actions.lock to your CODEOWNERS file makes sure the right team reviews those diffs.
Enforcing the Lockfile: CI Checks, Pre-Commit Hooks, and Org Policy
A lockfile only protects you if something checks it. These are the modes:
| Command | Writes | Network | Use for |
|---|---|---|---|
gh actions-lock |
yes | yes | Onboarding and fixing the lockfile |
gh actions-lock --relock |
yes | yes | Advancing moved refs to current upstream commits |
gh actions-lock --verify |
no | yes | CI: the same checks as a fix run |
gh actions-lock --verify-local |
no | no | Pre-commit: every uses: has a lockfile entry |
gh actions-lock --no-fix |
no | yes | Read-only report |
In CI, run gh actions-lock --verify on pull requests and make it a required check. It checks provenance before merge, and the runner checks it again at job start. The repository ships a ready-made verify-lockfile.yml you can copy and adapt.
--verify-local is the fast offline gate for a pre-commit hook. It makes no network calls and needs no authentication, but it only confirms every action reference has a lockfile entry, not that the entry is trustworthy. That's the right trade-off locally, and the wrong one as your only check.
For several repositories, the Require lockfile policy lets enterprise, organization and repository admins mandate lockfiles instead of relying on every team to wire up that CI step. GitHub's rollout advice is worth following: pilot it first. Evaluate mode, which is only available on GitHub Enterprise Cloud, shows you what would be blocked before anything breaks. The policy blocks runs of targeted workflows that aren't onboarded, including workflows with no uses: dependencies. It also blocks a calling workflow when a reusable workflow it calls isn't onboarded in its own repository. It's a separate policy from "Require actions to be pinned to a full-length commit SHA", though an onboarded workflow satisfies both.
What a GitHub Actions Lockfile Does Not Do
Locking a dependency and vetting it are different jobs, and GitHub says so directly: "Locking does not determine whether the dependency itself is safe, so review dependencies before locking them." A lockfile guarantees the code that ran during your review is the code that runs on every later job. It says nothing about whether that code was safe to run in the first place.
The README also lists limits the announcement doesn't mention:
- Reusable workflows called from another repository aren't locked.
- Container images aren't locked, and any image without an
@sha256:digest produces a warning. - A lockfile change that moves a floating ref (
v4,main) back to an older commit in its history isn't detected, so review lockfile diffs or pin exact tags.
And it doesn't replace restricting GITHUB_TOKEN permissions or requiring review on workflow changes. It closes one gap, tag drift after review, not the whole supply chain.
Lockfiles vs Manual SHA Pinning: What Changes, What Doesn't
If you've been pinning actions to a SHA by hand, the practice our GitHub Actions security guide recommends, that wasn't wrong. GitHub has historically recommended it too. But for repositories that adopt a lockfile, GitHub now says SHA pinning inside workflow files is no longer necessary. The lockfile is the better option because it covers transitive dependencies, keeps your workflow YAML easier to read, and records the identity of the source repository. An onboarded workflow also satisfies the "Require actions to be pinned to a full-length commit SHA" policy.
Two exceptions keep manual pinning alive. Calls to reusable workflows still need a full commit SHA by hand, because the lockfile doesn't cover them. And workflows you haven't onboarded yet still rely on whatever pinning you've already got.
For teams starting from zero, skip straight to the lockfile. For teams with hand-pinned SHAs everywhere, you can keep them while you migrate, and drop them once each workflow is onboarded.
Frequently Asked Questions
Q: Is the GitHub Actions lockfile still in preview?
A: No. It was in public preview from October 14, 2026 and became generally available on October 28, 2026, on GitHub Enterprise Cloud (including data-residency tenants), Team and Free plans. GitHub Enterprise Server isn't supported.
Q: Does a GitHub Actions lockfile replace manually pinning actions to a SHA?
A: For onboarded workflows, mostly yes. GitHub says SHA pinning inside workflow files is no longer necessary once a repository adopts a lockfile. The exception is reusable workflow calls (jobs.<id>.uses), which the lockfile doesn't pin, so those still need a full commit SHA.
Q: Does the lockfile cover reusable workflows?
A: No. A reusable workflow's dependencies are covered by the lockfile of the repository where it's defined, not the caller's. If you call shared workflows from another repository, that repository needs its own lockfile.
Q: Does Dependabot work with actions.lock?
A: Yes, for workflows you've already onboarded. When Dependabot bumps an action there, it regenerates the matching lockfile entry in the same pull request. It won't onboard a workflow that has no entries yet.
Q: What happens if an upstream action legitimately releases a new version?
A: Branch and partial-version refs keep their locked commit and give a warning; run gh actions-lock --relock after checking the upstream history. If a locked commit is no longer in the ref's history, only --accept-moved clears it, and GitHub says never to use that flag in automation.
Quick Summary:
- GitHub Actions workflow lockfiles went generally available October 28, 2026 (public preview from October 14) on Enterprise Cloud, Team and Free plans
-
.github/workflows/actions.lockpins every action and its transitive dependencies to a reviewed commit, but not reusable workflow calls (jobs.<id>.uses) - Install with
gh extension install github/gh-actions-lock, then rungh actions-lock, and review the full diff: it also rewritesuses:refs to specific versions - Local
./actions are a blocking error until you pass--migrate-local-actionsor--ignore-local-actions - After onboarding, allowed-actions policies check locked SHAs, so tag-based allowlist entries stop matching
- Use
--verifyin CI and--verify-localas an offline pre-commit gate;--relockadvances moved refs, and--accept-movedis never for automation - Container images and floating-ref rollbacks aren't covered, and locking isn't vetting: review dependencies before you lock them
If you already pin actions by hand, a GitHub Actions lockfile is the next step. Onboard your riskiest workflow first, fix the allowlist, add --verify to CI, and keep calling reusable workflows by full SHA.
Top comments (0)