Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

GitLab CI Error Guide: 'fatal: reference is not a tree' Shallow Clone Failure

Fix GitLab's 'reference is not a tree' and 'did not receive expected object': raise GIT_DEPTH, unshallow, fetch the ref, and adjust the project clone depth.

  • #gitlab-cicd
  • #troubleshooting
  • #errors
  • #git

Exact Error Message

The job fails inside a git operation, not your script:

$ git checkout 4f9a2c1
fatal: reference is not a tree: 4f9a2c1d8e3b5a7f9c0e1d2b3a4c5e6f7a8b9c0d
ERROR: Job failed: exit code 128

Closely related variants from the same root cause:

fatal: did not receive expected object 4f9a2c1d8e3b5a7f9c0e1d2b3a4c5e6f7a8b9c0d
fatal: not a git repository (or any of the parent directories): .git

The exit code is 128 — git’s own “fatal error” code — which tells you the failure is in git plumbing, not in your build commands.

What the Error Means

By default, GitLab runners do a shallow clone: they fetch only the most recent commit (or a small number, set by GIT_DEPTH) to keep checkouts fast. A shallow clone is a truncated history — git only has the objects within the depth window.

fatal: reference is not a tree: <sha> means you asked git to use a commit (checkout, merge-base, diff, describe, a tag) whose object was never fetched because it falls outside the shallow window. Git knows the SHA exists conceptually but doesn’t have the actual tree object on disk, so it refuses.

fatal: did not receive expected object is the same story during fetch — a referenced object wasn’t transferred. fatal: not a git repository appears when GIT_STRATEGY: none skipped the clone entirely but a later command still expects a working .git.

Common Causes

  • GIT_DEPTH too shallow. The default depth (often 20) doesn’t include the older commit, tag, or merge base your job needs.
  • Checking out an old commit or tag that’s beyond the fetched window — common in deploy/rollback jobs that target a specific historical SHA.
  • git describe / git merge-base / git diff against a base (e.g. comparing to main or a release tag) when that base isn’t in the shallow clone.
  • Submodules whose pinned commits live outside the shallow depth of the superproject.
  • A force-push mid-pipeline that rewrote history, so the SHA the pipeline was created against no longer exists in the fetched range.
  • GIT_STRATEGY: none used on a job that nonetheless runs git commands.

How to Reproduce the Error

Pin a deploy job to an old SHA while the clone is shallow:

deploy-rollback:
  variables:
    GIT_DEPTH: "1"          # only the tip commit is fetched
  script:
    - git checkout v1.2.0   # tag is older than depth 1 → not fetched
fatal: reference is not a tree: a1b2c3d...

Or run a tag-based version command on a shallow clone:

version:
  variables:
    GIT_DEPTH: "5"
  script:
    - git describe --tags   # needs older tag objects outside the window
fatal: No tags can describe '<sha>'.
fatal: reference is not a tree: <sha>

Diagnostic Commands

Add a git-inspection block to the job to see exactly what was fetched:

script:
  - echo "GIT_DEPTH=$GIT_DEPTH GIT_STRATEGY=$GIT_STRATEGY"
  - git rev-parse --is-shallow-repository    # true => shallow clone
  - git log --oneline -n 30                  # how many commits do we actually have?
  - git rev-list --count HEAD                # total reachable commit count
  - git tag                                  # are the tags present?

What each tells you:

  • echo "$GIT_DEPTH" — confirms the active depth (empty means project default applies).
  • git rev-parse --is-shallow-repository — prints true if the clone is truncated.
  • git log --oneline -n 30 — shows how far back history actually goes; if the SHA/tag you need isn’t listed, it wasn’t fetched.

To confirm the fix interactively, deepen the clone in the job and retry:

git fetch --unshallow            # convert to a full clone
# or fetch just what you need:
git fetch --depth=100 origin
git fetch origin refs/tags/v1.2.0
git checkout v1.2.0              # now succeeds

git fetch --unshallow pulls the entire remaining history; git fetch origin <ref> grabs a specific branch/tag without a full deepen.

You can also check the project-level setting: Settings > CI/CD > General pipelines > Git shallow clone depth. A small value there is the global cause when individual jobs don’t override GIT_DEPTH.

Step-by-Step Resolution

1. Raise GIT_DEPTH on the affected job (or globally) so the needed commit is in range:

deploy-rollback:
  variables:
    GIT_DEPTH: "100"        # deep enough to include the target tag/commit
  script:
    - git checkout v1.2.0

2. Use a full clone when you need arbitrary history (tags, git describe, diffs against any base). Set depth to 0, which disables shallow cloning:

variables:
  GIT_DEPTH: "0"            # 0 = fetch full history (no shallow clone)

Set it globally at the top of .gitlab-ci.yml if most jobs need full history, or per-job to keep fast jobs shallow.

3. Fetch the specific ref instead of deepening everything — cheaper for one-off needs:

version:
  script:
    - git fetch origin "refs/tags/*:refs/tags/*"   # fetch all tags
    - git fetch --unshallow || true                # or unshallow if shallow
    - git describe --tags

4. Handle submodules by fetching them with adequate depth via the runner variable:

variables:
  GIT_SUBMODULE_STRATEGY: recursive
  GIT_SUBMODULE_DEPTH: 0    # full depth for submodules too

5. Avoid GIT_STRATEGY: none on git-using jobs. If a job runs git commands, give it a clone:

build:
  variables:
    GIT_STRATEGY: fetch     # not "none" — ensure .git exists

6. Set the project default appropriately. If many pipelines need history, raise Settings > CI/CD > General pipelines > Git shallow clone depth (set to 0 for full clones) rather than overriding GIT_DEPTH in every job.

Prevention and Best Practices

  • Make depth explicit for any job that touches tags, merge bases, or historical commits — don’t rely on the (small) default.
  • Use GIT_DEPTH: "0" for release/versioning and deploy-rollback jobs that may reference arbitrary history.
  • Keep fast jobs shallow (GIT_DEPTH: "1" or the default) and only deepen the specific jobs that need it, so you don’t slow every pipeline.
  • Fetch tags deliberately (git fetch origin "refs/tags/*:refs/tags/*") when running git describe — shallow clones omit tags by default.
  • Mirror the project setting to your needs: check the Git shallow clone depth value under CI/CD settings if jobs fail without an explicit GIT_DEPTH.
  • Beware mid-pipeline force-pushes to the running ref; they can orphan the SHA the pipeline was built against.

Frequently Asked Questions

What does reference is not a tree actually mean? You asked git to use a commit (via checkout, diff, describe, or a tag) whose object isn’t on disk. Because the runner did a shallow clone, that commit falls outside the fetched depth window. Git knows the SHA but never received the tree object, so it errors out with exit code 128.

How do I do a full (non-shallow) clone in GitLab CI? Set GIT_DEPTH: "0" — a depth of zero disables shallow cloning and fetches the full history. Put it in a job’s variables: for one job, or at the top level of .gitlab-ci.yml for all jobs. You can also set the project default to 0 under Settings > CI/CD > General pipelines > Git shallow clone depth.

Why does git describe --tags fail in CI but work locally? Shallow clones don’t fetch tags or older history by default, so git describe can’t find a tag to describe. Either raise GIT_DEPTH (or set it to 0) and fetch tags with git fetch origin "refs/tags/*:refs/tags/*", or run the command after git fetch --unshallow.

What’s the difference between GIT_DEPTH and the project’s clone depth setting? The project setting (Settings > CI/CD > General pipelines > Git shallow clone depth) is the global default for all pipelines. GIT_DEPTH in .gitlab-ci.yml overrides it per-job or per-pipeline. If jobs fail without an explicit GIT_DEPTH, the project default is too shallow.

My pipeline started failing with this error after a force-push. Why? A force-push rewrote the branch history while the pipeline was running, so the SHA the pipeline was created against no longer exists in the newly fetched range. Re-run the pipeline against the new HEAD, and avoid force-pushing to a ref with active pipelines.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.