Fix "fatal: shallow update not allowed" When Pushing from a Shallow Clone

intermediate๐Ÿ“ฆ Git2026-06-19| Git 2.x on Linux, macOS, Windows โ€” commonly triggered in CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI) using shallow clones with --depth flag

Error Message

fatal: shallow update not allowed
#git#shallow-clone#push#unshallow#ci-cd

The Error

You (or your CI runner) cloned with a depth flag, and now git push fails cold:

fatal: shallow update not allowed

This hits most often during release steps. The runner grabs a shallow clone to save bandwidth, then a later job tries to push a tag, a version bump, or a build-artifact branch โ€” and Git refuses flat out.

Why This Happens

Shallow clones are intentionally incomplete. Git tracks the cut-off point in a file called .git/shallow, which lists the boundary commits โ€” the ones whose parents were never downloaded.

When the remote receives your push, it walks the commit ancestry to verify everything fits together. Hit a boundary it has never seen, and it bails out rather than risk corrupting the history graph. Three situations trigger this:

  • You used git clone --depth N locally and are pushing to a full remote.
  • Your CI runner checks out with fetch-depth: 1 (the actions/checkout default) and a later step pushes tags or commits.
  • You're pushing from one shallow clone into another โ€” both sides have incomplete history and neither can fill the gap.

Diagnose First

One command tells you whether your copy is shallow:

git rev-parse --is-shallow-repository

true means yes. You can also inspect the boundary directly:

cat .git/shallow

If the file exists and has content, you're dealing with a shallow clone. Each line is a commit SHA marking the edge of what was downloaded.

Fix 1 โ€” Unshallow Before Pushing (Recommended)

Fetch the complete history, then push:

git fetch --unshallow
git push origin HEAD

This pulls every missing commit and deletes .git/shallow, converting the repo into a full clone. From this point, pushes work normally โ€” no further workarounds needed.

On older Git versions where --unshallow isn't available, use this instead:

git fetch --depth=2147483647
git push origin HEAD

The number 2147483647 is INT_MAX. Git treats it as "give me everything," which achieves the same result.

Fix 2 โ€” Change the CI Checkout Depth

For CI pipelines, the better answer is to stop shallow-cloning for jobs that push. Set depth to 0 on release and deploy jobs only โ€” your test and lint jobs can stay shallow.

GitHub Actions

- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # 0 = full history

GitLab CI

variables:
  GIT_DEPTH: 0

Jenkins (pipeline script)

checkout([
  $class: 'GitSCM',
  extensions: [[$class: 'CloneOption', shallow: false]],
  // ... other options
])

Fix 3 โ€” Targeted Fetch for Tags Only

Need to push a single tag but don't want to download gigabytes of history? Fetch just enough for the tag's commit:

git fetch --depth=1 origin main
git tag v1.2.3
git push origin v1.2.3

This works only when the tag points to a commit the remote already knows about. If you're pushing new commits the remote has never seen, go back to Fix 1 or Fix 2.

Fix 4 โ€” If the Remote Repository Is the Shallow One

Rare, but it happens when someone created a bare repo with git clone --depth N --bare. In that case, unshallow the server side:

# Run on the server
cd /path/to/remote.git
git fetch --unshallow

No server access? Recreate the remote from a full clone instead.

Verify the Fix

Confirm the repo is no longer shallow:

git rev-parse --is-shallow-repository
# Should print: false

Then retry the push:

git push origin HEAD
# or for a tag:
git push origin v1.2.3

No fatal: output means you're clear.

Prevent It in CI/CD

Shallow clones exist for one reason: speed. A depth-1 clone on a 50,000-commit repo can cut checkout time from 30 seconds to under 2. That trade-off makes sense for compile and test jobs that never touch history.

It breaks the moment a job needs to reason about the past โ€” pushing commits, creating tags, running git describe, generating changelogs, or diffing across releases.

Practical split: fetch-depth: 1 for test and lint, fetch-depth: 0 for release and deploy. Most CI systems let you set this per-job, so you keep speed where it matters and correctness where it's required.

On pipelines with many jobs, consider a dedicated checkout job that fetches full history and caches the .git directory. Downstream jobs restore from cache instead of cloning again.

Quick Reference

  • Local shallow clone โ†’ normal remote: git fetch --unshallow && git push
  • CI pipeline with depth: 1: set fetch-depth: 0 in the checkout step
  • Pushing a tag only: git fetch --depth=1 origin <branch> && git push origin <tag>
  • Remote is shallow: unshallow or recreate the remote

Related Error Notes