A pinned wrapper leaves a useful test case
GitHub’s workflow dependency lockfile preview, announced October 14, 2026, covers nested actions as well as direct dependencies. A workflow can pin a wrapper action to a full commit hash while the wrapper calls another action through a movable tag. That creates a useful adoption test: keep the wrapper fixed, move the nested action’s tag, and observe which revision executes. Sources: GitHub, GitHub Docs
GitHub stores the dependency graph in.github/workflows/actions.lock, recording resolved commits and numeric owner and repository identities; it checks repository identity at runtime. Every dependency of an onboarded workflow must appear, and locked commits must exist within a source-repository branch. Dependencies inside a reusable workflow follow the defining repository’s lockfile, making shared-workflow repositories a separate adoption task. GitHub’s secure-use guidance recommends lockfiles for repositories adopting them and retains full-length commit pinning guidance for others. Sources: GitHub Docs
Build a nested action with a visible revision marker
Use three disposable repositories under your control: a leaf action, a wrapper action, and a consumer workflow. Commit the following action.yml in the leaf repository as revision A. Create a lightweight major-version tag at that commit, then push the branch and tag. Avoid full semantic-version tags initially: generation can narrow references to more specific versions, changing the condition under test. Sources: GitHub Docs
name: Lockfile leaf probe
description: Emits a recognizable revision marker
runs:
using: composite
steps:
- shell: bash
run: echo 'LOCKFILE_PROBE=A'In the wrapper repository, commit the following action.yml and record its full commit hash as P. Substitute your repository owner for the example owner. Keep this wrapper commit unchanged throughout the comparison; only the leaf tag should move.
name: Lockfile wrapper probe
description: Calls a deliberately movable nested dependency
runs:
using: composite
steps:
- uses: LAB/lock-leaf@v1In the consumer repository, create.github/workflows/probe.yml using the configuration below. Replace the owner and placeholder with the wrapper’s full commit hash. Dispatch the workflow and save its run URL and logs showing revision A. Preserve an unlocked control branch or separate consumer repository, and defer any enforcement policy that would prevent that control from running.
name: Nested dependency probe
on:
workflow_dispatch:
permissions:
contents: read
jobs:
probe:
runs-on: ubuntu-24.04
steps:
- uses: LAB/lock-wrapper@REPLACE_WITH_FULL_WRAPPER_SHALock the baseline, then move only the leaf tag
Install GitHub’s extension and onboard the probe with the commands below; GitHub documents selecting an individual workflow. Review the entire diff because generation may narrow references and migrate local action references. Confirm that both wrapper and leaf appear in the dependency graph, with the leaf locked to A and its major-version reference preserved. Record the tool version, generated schema version, and dependency identities before committing and running the locked baseline. Sources: GitHub Docs
gh extension install github/gh-actions-lock
gh actions-lock .github/workflows/probe.yml
git diffNext, change only the leaf’s output marker to identify revision B and commit it as a descendant of A on the same branch. In the disposable leaf repository, use the commands below to move the tag and inspect its remote target. Keep A reachable in branch history so the test isolates tag movement from lost commit reachability. Dispatch fresh runs of both consumers without changing their workflow files or the wrapper.
git tag -f v1 HEAD
git push origin HEAD
git push --force origin refs/tags/v1
git ls-remote origin refs/tags/v1This experiment has not been run; the table below gives expected outcomes to verify, not measured results. GitHub’s extension documentation says branch and partial-version movement does not automatically advance a locked commit. The marker test checks nested-action selection; identical action commits alone do not establish an identical build environment or identical network-fetched inputs. Retain each run’s consumer commit, wrapper hash, leaf tag target, locked leaf hash, runner version and image, run URL, marker, and error text. Sources: GitHub
Table
Expected outcomes for the partial-version tag experiment
| Test | Expected outcome to verify |
|---|---|
| Unlocked consumer, wrapper still pinned to P | B executes through the nested @v1 reference. |
| Locked consumer, lockfile unchanged | A executes. |
| Ordinary lock generation | Leaf remains locked to A. |
| Online verification with A still an ancestor | Verification accepts the preserved pin. |
| Explicit relock, reviewed and committed | Leaf advances to B; subsequent execution prints B. |
Separate verification from intentional updates
Run each command below in a separate clean copy of the same locked checkout so one operation cannot affect another. Offline verification checks coverage without establishing that the recorded pins are trustworthy. Online verification also checks that preserved branch and partial-version pins remain in the reference’s history. Ordinary generation keeps existing pins, including transitive pins, while explicit relocking re-resolves legitimately moved references. Sources: GitHub
gh actions-lock --verify-local
gh actions-lock --verify
gh actions-lock
gh actions-lock --relockSave exit codes and diffs for every operation, including successful ones. Inspect whether relocking advances the leaf to B while retaining wrapper P, and whether it changes references or other metadata. Review and commit the update before dispatching the final consumer run. The extension documents all-or-nothing generation writes when an unfixable error occurs, so preserve the working-tree state after failures too. Sources: GitHub
Then repeat the fixture with a full-version leaf tag. The extension documentation says a full-version tag must still point to its locked commit and that failed checks block the run. Its normal pin checks mirror runner checks at job start, making the location and wording of the failure useful evidence. Test lost reachability separately: accepting a moved reference whose old commit is no longer reachable upstream requires an additional explicit option. Sources: GitHub
- Retag the full-version fixture after locking; record whether execution stops before the marker step and capture the command-line finding.
- Add a dependency to an onboarded workflow without regenerating its lockfile; capture whether coverage checks reject it.
- Regenerate from the workflow inputs, review the diff, and rerun to check that coverage is restored.
Make update ownership part of adoption
Follow a successful marker test with an update exercise before broad enforcement. Dependabot regenerates matching lockfile entries for version updates to onboarded workflows, but does not onboard uncovered workflows. Test transitive floating-tag movement separately rather than assuming it triggers an automatic update pull request. Renovate documents experimental support that regenerates existing lockfiles after action updates, skips uncovered workflows, and suppresses narrowing and local-action migration. Those choices can produce different diffs from a default manual invocation. Sources: GitHub, GitHub Docs, Renovate Docs
GitHub recommends piloting enforcement with selected repositories and workflows; Evaluate mode is Enterprise-only. Its enforcement guide recommends pull-request verification and code-owner review of the lockfile backed by required approvals. Check allowlists during the pilot because onboarding switches matching to locked commit hashes, potentially breaking tag-specific entries. Assign an owner to shared-workflow repositories as well as consumers, since their internal dependencies follow their own lockfiles. Sources: GitHub Docs
GitHub cautions that locking does not establish dependency safety; source review and least-privilege tokens remain relevant. Make the pilot’s final exercise a necessary dependency update: identify the revision, review the relock, merge it, and confirm execution. Measure how long that process takes and who can complete it. Use the result to assign update responsibility before expanding enforcement. Sources: GitHub, GitHub Docs