GitHub Actions Lockfiles: What Gets Pinned
An unchanged workflow file can still run different dependency code on the next CI run: tags and branches can move. GitHub Actions workflow dependency lockfiles separate the readable reference in YAML from the commit selected for execution. They address dependency-selection drift; they do not decide whether the selected dependency is trustworthy.
Contents
- What entered public preview
- Why one tag does not identify one immutable code snapshot
- Review a dependency graph, not just the first level
- Observe drift and missing entries in a local model
- Check coverage and trust as separate results
- Separate onboarding review from update review
- A passing check does not reproduce the entire build
- Design for maintenance, not just the first locked run
- Sources and verification scope
For teams maintaining CI with readable version tags, the important questions extend beyond generating a file. Which dependencies are covered, what should reviewers inspect during updates, and which build inputs remain outside the lock? 阅读中文版.
What entered public preview
GitHub’s October 14, 2026 announcement introduced workflow dependency lockfiles in public preview. Its corrected availability includes Enterprise Cloud, Enterprise Cloud with data residency, Team, and Free. Earlier summaries differed from the corrected announcement, so the correction matters.
The official tooling generates .github/workflows/actions.lock with recorded dependency commits. An onboarded workflow uses those locked commits rather than automatically following a subsequently moved tag. That adds a reviewable constraint between the code selected during review and the code selected during execution.
The practical benefit is reduced uncertainty. When CI behavior changes, maintainers can investigate recorded commits and dependency changes rather than first guessing whether a readable tag silently changed its target. A locked version can still contain defects, however, and a fixed identity is not a reason to keep a dependency indefinitely.
Why one tag does not identify one immutable code snapshot
Treat a tag as a name rather than the content itself. In a fictional example, demo/lint@v2 points to revision A during review and later points to B upstream. The downstream repository has not changed its YAML, yet resolving that name at run time may select B. This illustrates reference drift; it is not a claim that a real project experienced this event.
Review: tag → A
Upstream later: tag → B
Unlocked run: B
Locked selection: A
Adopting a lockfile changes the selection path. The workflow retains its readable reference while execution uses a recorded commit. The name remains convenient for maintenance, and the code identity can be traced to a review decision. An update review can examine both why the reference changes and which concrete revisions are being exchanged.
A reference that still reads v2 is therefore not enough reason to skip reviewing a change. Conversely, an unchanged commit does not establish that the execution environment, input data, or external services stayed fixed. Separating those dimensions prevents an investigation from attributing every difference to the lockfile.
Review a dependency graph, not just the first level
An action can use other actions. Looking only at top-level workflow references can leave lower-level changes unnoticed. The official concept documentation describes transitive action coverage and a boundary for reusable workflows: their internal dependencies are covered by the lockfile in the repository defining that reusable workflow.
Repository workflow
Top-level action
Actions used by that action
Reusable workflow: inspect its defining repository
“This repository has a lockfile” consequently leaves important questions unanswered. Which workflows were onboarded? Which references fall within this repository’s coverage? Which dependencies belong to another maintained boundary? A large generated file can still fail to explain the path that actually runs.
A useful review follows the entry point: identify the workflow, its actions, any reusable-workflow boundary, and the maintainers responsible beyond it. Do not read transitive coverage as a promise that every repository boundary and arbitrary runtime download is locked through one file.
Observe drift and missing entries in a local model
The following Python program uses fictional dependency and revision identifiers. It demonstrates two properties: changing an upstream reference does not change a locked selection, and a missing required entry prevents a complete selection. It is neither GitHub tooling nor the actions.lock format, and it does not access the network or modify a repository.
# Fictional identifiers and graph: not GitHub's lockfile format.
ROOT = "demo/build@v1"
CHILD = "demo/lint@v2"
graph = {ROOT: [CHILD], CHILD: []}
locked = {ROOT: "reviewed-A", CHILD: "reviewed-B"}
upstream = {ROOT: "reviewed-A", CHILD: "reviewed-B"}
def dependencies(graph, roots):
seen, active = set(), set()
def visit(ref):
if ref in active:
raise ValueError("cycle requires review")
if ref in seen:
return
if ref not in graph:
raise ValueError("unknown dependency")
active.add(ref)
for child in graph[ref]:
visit(child)
active.remove(ref)
seen.add(ref)
for ref in roots:
visit(ref)
return seen
def selected_commits(graph, roots, locked):
needed = dependencies(graph, roots)
missing = needed - locked.keys()
if missing:
raise ValueError("missing lock entries: " + str(sorted(missing)))
return {ref: locked[ref] for ref in sorted(needed)}
before = selected_commits(graph, [ROOT], locked)
upstream[CHILD] = "later-C"
assert upstream[CHILD] != locked[CHILD]
assert selected_commits(graph, [ROOT], locked) == before
for bad_graph, bad_lock in [
(graph, {ROOT: "reviewed-A"}),
({ROOT: [CHILD], CHILD: [ROOT]}, locked),
({ROOT: ["unknown/ref@v1"]}, locked),
]:
try:
selected_commits(bad_graph, [ROOT], bad_lock)
except ValueError:
pass
else:
raise AssertionError("incomplete graph was accepted")
assert selected_commits({}, [], {}) == {}
print(before)
The first selection records reviewed-A and reviewed-B. Moving the fictional upstream child to later-C leaves that selection unchanged. The assertions also cover a missing child entry, a cycle, an unknown node, and empty roots. The code was executed locally; no real GitHub onboarding or platform security check was exercised.
Rejecting a cycle is a conservative choice in this teaching checker, not a statement about GitHub’s algorithm. The useful distinction is between a top-level entry and coverage of the required dependency set. A real lockfile also participates in code-identity validation, which this graph-and-map model cannot replace.
Check coverage and trust as separate results
The local model returns a selection whenever every dependency has a recorded revision. Replace reviewed-B with a never-reviewed label and coverage can still pass. The checker does not contact a source repository or establish which code that label represents. Complete coverage is necessary for the model, but it cannot establish that the entries are trustworthy.
The official project distinguishes these checks: --verify-local checks entry coverage offline without establishing trust, while --verify provides further validation for CI. Consult the exact version’s documentation before adopting it. The Python program here demonstrates only part of the coverage idea; calling its output a verified software supply chain would overstate the evidence.
Keep two review results: whether the dependency graph has omissions, and whether the recorded source and revision have supporting evidence. A single “verification passed” leaves the next maintainer unable to tell which layer was checked. Tool reports, human review, and platform execution results should retain their distinct meanings.
Separate onboarding review from update review
The official gh-actions-lock project provides generation and verification tooling. The public preview is evolving, so inspect the documentation for the tool version in use and generate the file through the tool rather than writing a plausible-looking format manually. No extension was installed or repository policy enabled for this article.
During first-time onboarding, a successful generation command is only an intermediate result. Inspect the diff: confirm the intended workflows, explain the sources and selected commits, and assign responsibility for newly visible transitive dependencies. Then run ordinary CI and check that its execution path still matches the intended job.
A later update answers a different question: why should this workflow move from the old commit to the new one now? The reason may be a fix or routine maintenance. Review the reference change together with the locked revision change. A machine-generated diff is not automatically exempt from review; automation saves preparation time rather than deciding which change a maintainer should trust.
A passing check does not reproduce the entire build
Fixing an action revision does not automatically fix every tool that its code downloads at run time. Package indexes, base images, runner environments, parameters, and external services can also affect build results. This is a boundary in the engineering argument, not a reason to dismiss dependency locking.
Investigate in two layers. First confirm that workflow dependencies are selected according to their records. Then inventory additional inputs consumed during the build. A variable URL or an unpinned package version in the second layer needs its own appropriate locking and validation mechanism. Giving that input an explicit owner is more useful than assuming the workflow lock covers it.
A fixed commit and least privilege are also separate controls. Even a reviewed dependency should not receive unnecessary write access or credentials. The site’s GitHub App least-privilege article explains permission boundaries, but its App authorization model does not replace documentation for configuring an Actions token.
Design for maintenance, not just the first locked run
The value of a lockfile depends on the update process around it. Locking without updating preserves identity while potentially preserving known problems. Automatically merging every regenerated file weakens review in a different way. Assign ownership, distinguish urgent fixes from routine upgrades, and retain an explanation of why each update was accepted.
Begin with one workflow whose responsibility and dependencies are understandable. Exercise onboarding, ordinary execution, updates, and failure handling before expanding adoption. Mark paths that remain outside coverage explicitly rather than treating the existence of a lockfile as proof that every workflow has been governed.
A useful handover should answer which commits a run selected, who accepted the change, where coverage ends, and how the next update will be proposed. The lockfile makes those answers easier to substantiate. The operational value comes from aligning review and execution, not from adding another file to the repository.
Sources and verification scope
GitHub announcement, October 14, 2026, including its availability correction; workflow dependency lockfile concepts; official gh-actions-lock project. The Google News item was timestamped October 14 at 13:49:15 UTC, before the planned article slot. Dependency names, revisions, and graphics are original teaching material. No real workflow or build-performance benchmark was run.
Support
If the explanations or examples helped you solve a problem or save time, you’re welcome to support this site with a voluntary tip.
Click a QR code to enlarge. More options: support page。


