A workflow using actions/download-artifact@v4 can run different code tomorrow even when nobody changes a line in your repository. By contrast, Astral’s uv project pins download-artifact to the 40-character commit 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c and leaves a human-readable # v8.0.1 note beside it.
That difference is not cosmetic. A GitHub Actions uses: reference is executable supply-chain input: GitHub resolves it before your job downloads the artifact that a later deployment, test, or packaging step may trust. The right reference strategy depends less on whether your team likes short YAML and more on one question: can an upstream change become production-impacting before a pull request in your repository reviews it?
Three references that look similar but behave differently
Consider three ways to call the same action. All may work today, but they make fundamentally different promises about what will execute on the next run.
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v4.3.0
- uses: actions/download-artifact@d3f86a1...
A major tag such as v4 is deliberately moving. The action maintainers can advance it to a newer v4 release, which is convenient for fixes but means the resolved commit can change without a change in your repository. An exact version tag such as v4.3.0 communicates more intent, but it is still a Git reference unless repository controls make that tag non-movable.
A full commit SHA identifies one object in Git history. The SHA is not a release channel and cannot silently advance to another commit. The omitted middle category matters: an exact release tag backed by repository tag protections or an immutable-release policy can provide a stable, named release reference. It is easier to read than a SHA, but its security depends on the enforcement behind that name, not on the fact that it contains three version components.
Why download-artifact makes the tradeoff visible
actions/download-artifact is a useful example because workflow authors often treat it as plumbing: upload in one job, download in another, then test or publish the files. But changes in artifact behavior can be consequential. The published notes for v8.0.0 describe direct-download behavior and a change to decompression handling, including the skip-decompress parameter.
That means an upgrade is not merely a security patch. A job expecting an extracted directory can behave differently from a job that expects a downloaded archive. The action’s v4.3.0 release notes also identify a concrete release commit, d3f86a1, and mention the new artifact-ids input. These are examples of why a reference has two jobs:
- Security job: prevent an unreviewed upstream ref movement from changing your workflow.
- Change-management job: make intentional action upgrades visible, reviewable, and reversible.
Pinning to a SHA solves the first job most directly. It makes the second job more disciplined because every upgrade becomes a repository diff. The cost is that somebody—or an update bot—must create that diff.
Mutable tags optimize for convenience, not evidence
Use @v4 when you consciously accept a floating dependency. It is readable in a pull request, easy for new contributors to recognize, and usually keeps a workflow within a major-version line. It is also insufficient evidence of what ran during a particular historical workflow execution: the tag name is a label, not the immutable identity of the code it resolved to.
The common argument for a major tag is “we need security fixes automatically.” That can be reasonable for a low-impact CI workflow, but it combines two unrelated decisions: trusting maintainers to publish updates and allowing those updates to enter your workflow without your review. You can trust the maintainers and still require a pull request for each new commit.
The second-order cost appears during incident response. If a build started failing after an upstream change to @v4, your repository history has no dependency update commit to inspect or revert. You must first determine what the tag resolved to at the time. With a SHA pin, the before and after values are already in the pull request diff.
Immutable release references are useful, but inspect the guarantee
An exact release reference such as @v4.3.0 is a better operational label than a raw hash. Reviewers can immediately connect it to release notes, and release semantics can distinguish production releases from pre-releases. GitHub’s release documentation also describes a “latest release” label and notes that immutable releases restrict modification, replacement, or deletion of release content.
However, do not equate a GitHub Release page with an immutable action reference automatically. A release is GitHub metadata attached to a tag; an action runner resolves the ref in owner/repository@ref. Your policy needs to answer a narrower question: is this specific ref prevented from being moved, and who can change that protection?
For an organization-controlled action, a protected or otherwise enforced release-tag process can be a practical middle ground. For a third-party action, you are relying on controls outside your repository. That may be acceptable, but it is a different trust boundary from a SHA committed into your own default branch.
Decision matrix: choose based on the workflow’s blast radius
| Reference style | Security against ref movement | Review readability | Dependabot-style updates | Emergency rollback |
|---|---|---|---|---|
@v4 mutable major tag |
Low: the named ref can advance without a repository commit. | Excellent: the supported major line is obvious. | Minimal upgrade work, but upstream changes may arrive outside a pull request. | Weak: pinning history may not identify the change; switching refs is a manual intervention. |
@v4.3.0 enforced immutable release tag |
Medium to high, if the tag’s immutability is actually enforced. | Excellent: reviewers see the release version. | Good: a bot can propose the next release-tag change. | Good: revert from one known release tag to the prior tag. |
| Full 40-character commit SHA | High: the workflow names one exact Git object. | Low alone; high when paired with a version comment. | Good: bots can submit explicit SHA replacement pull requests. | Excellent: revert one reviewed SHA change. |
This table is not a universal ranking. A nightly lint job with no credentials and no release authority may reasonably use a major tag. A workflow that downloads a build artifact and then publishes a package, signs a release, or deploys infrastructure should normally use a full SHA pin. The deployment permission and reachable secrets matter more than the action’s apparent simplicity.
The practical default: SHA pin plus a release comment
The best compromise for many production repositories is the pattern visible in uv’s smoke-test workflow:
- name: Download binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: uv-macos-x86_64-${{ inputs.sha }}
The exact hash supplies the execution identity. The comment supplies the review identity. During a code review, a maintainer can see that the update intends to use v8.0.1 rather than having to recognize a 40-character hexadecimal string. During an investigation, the hash remains the authoritative value.
Keep the comment accurate. An incorrect comment creates a dangerous split between what reviewers believe and what runners execute. Treat the comment as part of the dependency update, not decoration. The release page and its release commit are useful places to verify the mapping before merging.
Also use the full SHA, not a short prefix such as d3f86a1, in workflow YAML. A short hash is convenient in release notes and GitHub’s interface, but a full hash gives the workflow an unambiguous object identity.
How Dependabot-style updates fit a pinned policy
SHA pinning does not mean hand-maintaining hashes forever. The productive model is to let an automation tool open a pull request that changes both the commit SHA and the adjacent release comment, then review it like any other dependency update. GitHub’s changelog includes Dependabot improvements around access to GitHub-hosted registries; the broader lesson is that automated dependency maintenance is part of supply-chain hygiene, not an alternative to it.
Your reviewer still needs to check more than the new version label. For download-artifact, use a short checklist:
- Confirm the new full SHA corresponds to the intended upstream release.
- Read the release changes for input, output, or extraction behavior changes.
- Check whether the workflow uses artifact names, paths, or assumptions affected by that change.
- Run the workflow on a branch or pull request before merging.
- Keep the prior SHA available in the Git diff for a one-commit rollback.
This is the advantage that floating @v4 cannot offer: updates may remain frequent, but each one has an approval record, test result, and reversible commit.
Work through a release workflow from artifact to deployment
Suppose a repository has three jobs: build uploads a package, verify downloads it and runs tests, and release downloads the same package before publishing it. The build job has no publication credential; the release job has the permission or secret that makes an external release possible.
- Pin
download-artifactto a full SHA inrelease. This job has the largest blast radius because it handles the artifact immediately before publication. - Use the same SHA in
verify. Testing different action code than the release job is an avoidable source of mismatched behavior. - Decide separately whether the low-risk build job can float on a major tag. In most teams, consistency is cheaper: use the same pinned revision everywhere.
- When a new action release is needed, update all three references in one pull request and run the complete path.
The overlooked failure mode is partial upgrades. If verification uses a newer action version with altered extraction behavior while release stays on an older one, a green verification job does not prove that the release job consumed the same file layout.
Do not confuse artifact retention with action provenance
Artifact files and action code are separate supply-chain concerns. Giving an artifact a precise name such as uv-macos-x86_64-${{ inputs.sha }} helps a later job select the intended build output. The v4.3.0 release notes’ artifact-ids input points toward an even more explicit selection mechanism when an artifact ID is available.
But a precise artifact name does not lock the behavior of the downloader, and a pinned downloader does not prove that the artifact is the expected build output. Secure workflows need both kinds of identity:
| Thing being identified | Useful identifier | Question it answers |
|---|---|---|
| Action implementation | Full commit SHA | Which downloader code executed? |
| Action release intent | Version comment, such as # v8.0.1 |
Which upstream release was approved? |
| Artifact selection | Specific name or artifact ID | Which build output did this job retrieve? |
| Build source | Commit SHA in the artifact name or metadata | Which repository revision produced it? |
Teams often solve the artifact-name problem and assume the workflow is therefore reproducible. It is only reproducible if the action reference is stable too.
Adopt a policy this week
Start with one repository that has a release, deployment, package-publishing, or signing workflow. Search its workflow files for action references that use a major tag:
grep -R "uses: .*@v[0-9]" .github/workflows
Then apply a simple rule: any third-party action reachable from a job that can publish, deploy, alter cloud infrastructure, or access a high-value secret gets a full 40-character SHA plus a release-version comment. For lower-risk workflows, choose deliberately between a mutable major tag and the same SHA policy; do not let a short YAML line make the decision accidentally.
Finally, test one rollback. Make a branch that changes a pinned download-artifact SHA, run the workflow, and revert that single commit. If your team cannot identify the old SHA, validate the new release, and reverse the change in a few minutes, the problem is not pinning syntax. It is that your action-update process has not yet become a routine dependency-management practice.