Because the default checkout fetches a single commit. A diff against the base branch needs the base commit in the local history, and a shallow clone does not have it. Raise the fetch depth, fetch the base ref explicitly, or use the platform's changed file list instead.
What the default does
The documentation for the GitHub checkout action says fetch-depth defaults to 1, meaning only a single commit is fetched, for the ref or SHA that triggered the workflow. Setting it to 0 fetches all history for all branches and tags. The docs show fetching HEAD^ with a depth of 2.
Most tools that find changed packages run a git diff between two refs. With one commit in the clone, the base ref is missing, and the command fails or, worse, returns nothing.
Symptoms
| Symptom | Likely cause |
|---|---|
| Error about an unknown revision | Base commit not in the shallow clone |
| No packages detected as changed | Diff failed quietly and returned empty |
| Everything scanned every time | Tool fell back to a full run |
| Works locally, fails in CI | Your local clone has full history |
The quiet empty result is the dangerous one. A scan that finds nothing changed reports a pass and ships unreviewed dependency changes.
Three fixes
- Full history. Set fetch-depth to 0. Simple, but slow on very large repositories.
- Fetch the base. Keep the depth small and fetch the target branch explicitly before running the diff.
- Use the changed file list from the platform. Pull request events carry the base and head commit SHAs, and many tools accept them directly.
- uses: actions/checkout@v4
with:
fetch-depth: 0
Pin the action to the version you have reviewed in your own setup.
Guard against the silent pass
Add a check that fails the job if the diff command errors or if the base SHA is not found. Also log the list of changed files in the job output, so a reviewer can see what the scan believed had changed. For pull requests, remember that GitHub computes the changed file list using a three dot diff against where the topic branch last synced with its base.
Sparse checkout is a separate choice
The checkout action also offers sparse checkout, which fetches only the patterns you name. That helps speed but means files outside the pattern are absent, so a scan run there cannot see them. Sparse checkout is not access control either, see sparse checkout is not access control.
Keep a full scan anyway
Diff based scans save time but depend on correct history. Run a scheduled full scan so a bad diff on one day does not become a blind spot for weeks.
Safeguard can scan on pull requests and in the pipeline, with policies that gate on findings. The free scan at https://gold.safeguard.sh/scan needs no setup.
Try it yourself
In the gate demo, look at what information the check needs to decide pass or fail. Missing history means missing input.
Click through it at your own pace, or open it full screen.
Quick check
What is the default fetch depth in the checkout action?
1, which fetches a single commit. A value of 0 fetches all history.
Why is an empty changed-file list risky?
It can mean the diff failed quietly, so the scan skips packages that did change.
Sources
Last reviewed: 19 October 2026.