How actions/checkout Validates Moved Tags After Fetching: A Deep Dive into the Verification Logic
When checking out a tag, actions/compare validates that the tag still points to the same commit SHA after fetching by comparing the pre-fetch resolved SHA with a post-fetch rev-parse, aborting the workflow if the tag has moved.
The actions/checkout action is the standard method for cloning repositories in GitHub Actions workflows. When you specify a tag as the checkout target, the action implements a critical safeguard against race conditions where a tag might be repointed to a different commit after the workflow triggers but before the fetch completes.
Why Tags Require Post-Fetch Validation
In Git, tags are mutable references that can be deleted and recreated or force-updated to point to different commits. The validation logic in src/git-source-provider.ts explicitly addresses the scenario where "the tag was moved after the workflow was triggered" (line 204). Without verification, a workflow could end up executing code from an unexpected commit if a maintainer moved the tag during the job queueing time.
The Three-Phase Validation Pipeline
The verification process is implemented in src/git-source-provider.ts and follows a strict sequence to ensure integrity.
Phase 1: Capturing the Expected Commit SHA
Before any network operations begin, the action resolves the requested tag to a specific commit SHA using git.revParse(). This creates an immutable baseline representing the state of the repository at the moment the workflow started. The code stores this expected SHA for later comparison.
Phase 2: Fetching with Tag RefSpecs
When the fetch-tags input is set to true, the action constructs a specific refspec in src/ref-helper.ts (line 6) as +refs/tags/*:refs/tags/*. This refspec forces the fetch operation to update local tag references even if they already exist. The implementation in src/git-command-manager.ts ensures that tags are explicitly fetched when requested, rather than using the default --no-tags behavior.
Phase 3: Post-Fetch SHA Verification
After the fetch completes, the action performs the critical validation. According to the comments in src/git-source-provider.ts:
- Line 196 notes that "when all history is fetched, the ref we're interested in may have moved to a different..."
- Line 221 explicitly states: "For tags, verify the ref still points to the expected commit."
The implementation resolves the tag a second time using git.revParse(\${ref}^{commit}`)` and compares this fresh SHA against the value captured in Phase 1. If the SHAs differ, the action throws an error:
Error: Tag <tag> was moved after the workflow was triggered.
This immediate failure prevents the workflow from proceeding with potentially incorrect or malicious code.
Practical Workflow Example
Here is a configuration that enables the moved-tag validation:
name: Release Build
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-tags: true
fetch-depth: 0
If the tag is repointed between the workflow trigger and the checkout execution, the step fails immediately with the moved-tag error, protecting your supply chain.
Key Implementation Files
src/git-source-provider.ts(lines 196-221): Contains the core verification logic that implements the comment "For tags, verify the ref still points to the expected commit" and performs the SHA comparison.src/ref-helper.ts(line 6): Defines the tag refspec+refs/tags/*:refs/tags/*used whenfetch-tagsis enabled.src/git-command-manager.ts: Implements therevParse()method used to resolve tags to their underlying commit SHAs before and after fetching.src/input-helper.ts(lines 129-132): Processes the booleanfetch-tagsinput to determine whether the validation path should be executed.
Summary
- Pre-fetch resolution: The action captures the expected commit SHA before any network operations to establish a trusted baseline.
- Explicit tag fetching: When enabled via
fetch-tags: true, the action uses the refspec+refs/tags/*:refs/tags/*to ensure local tag references are updated. - SHA comparison: After fetching, the action re-resolves the tag and aborts the workflow if the commit SHA has changed from the expected value.
- Security safeguard: This validation prevents supply-chain attacks where a tag is moved to malicious code after the workflow starts but before checkout completes.
Frequently Asked Questions
What happens if a tag is moved during the checkout process?
The action throws Error: Tag <tag> was moved after the workflow was triggered. and fails the step immediately. This error originates in src/git-source-provider.ts when the post-fetch SHA comparison detects a mismatch.
Does actions/checkout validate moved branches as well?
No. The validation logic specifically targets tags because they are expected to be immutable release references. Branches are inherently mutable, so the action does not perform post-fetch SHA verification for branch refs.
How can I enable tag validation in my workflow?
Set fetch-tags: true in your checkout step inputs. Without this setting, the action uses --no-tags during fetch (as implemented in src/git-command-manager.ts), and the validation logic is never reached because the local tag reference remains unchanged.
Is this validation performed for both lightweight and annotated tags?
Yes. The validation works for both types because the code uses git rev-parse <ref>^{commit} to peel the tag to its underlying commit object. This ensures the SHA comparison is always performed against the actual commit, regardless of whether the tag is lightweight or annotated.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →