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

GitLab CI Error Guide: 'The remote end hung up unexpectedly' — Fix Git Clone Failures in Jobs

Quick answer

Fix 'fatal: the remote end hung up unexpectedly' in GitLab CI: tune shallow clone depth, HTTP post buffer, timeouts, and Git strategy so large-repo and LFS clones stop dropping mid-fetch.

  • #gitlab
  • #ci-cd
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this GitLab CI/CD error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

Every GitLab CI job begins with the runner cloning or fetching your repository into the build directory. When that Git transfer is interrupted before it completes, the job fails in the “Getting source from Git repository” section — before your script: ever runs — with:

fatal: the remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

Over HTTP you’ll often see the companion curl/RPC lines just above it:

error: RPC failed; curl 92 HTTP/2 stream 5 was not closed cleanly
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: expected flush after ref listing

The transfer started, then the connection was cut mid-stream. This is almost always about transfer size, memory, timeouts, or a proxy — not a corrupt repo.

Symptoms

  • The job fails in the “Getting source from Git repository” step; your scripts never execute.
  • Failures are intermittent — the same pipeline sometimes passes, especially on smaller MRs.
  • Large repositories, repos with big binaries, or Git LFS objects fail more often.
  • Failures correlate with a git fetch/git clone of a deep history or a runner behind a proxy/VPN.
  • Retrying the job sometimes succeeds, suggesting a flaky or throttled network path.

Common Root Causes

  • Repository or single objects too large — deep history or large binaries blow past HTTP buffer/timeout limits mid-transfer.
  • GIT_STRATEGY: clone on a huge repo — a full clone every job moves far more data than a shallow fetch.
  • Missing or too-deep GIT_DEPTH — no shallow clone means the runner pulls all history; too shallow can also break when a needed ref isn’t present.
  • HTTP post buffer / server limits — the default http.postBuffer is small; large packs get chunked in ways a strict proxy or gateway drops.
  • Proxy, load balancer, or gateway timeouts — an idle/duration cap (e.g. 60s) on a reverse proxy in front of GitLab cuts long transfers.
  • Runner memory pressuregit index-pack runs out of memory unpacking a large pack and the process dies (early EOF).
  • Git LFS interruptions — large LFS objects time out or hit smudge/bandwidth limits during checkout.
  • HTTP/2 quirks — some proxies mishandle HTTP/2 streams, producing curl 92 ... not closed cleanly.

Diagnostic Workflow

1. Confirm where it fails and how big the repo is. The error in “Getting source” (not in script:) points at the fetch. Measure the repo:

# Repo size and largest objects (run in the repo, or on the runner host)
du -sh .git
git count-objects -vH
git rev-list --objects --all |
  git cat-file --batch-check='%(objecttype) %(objectsize) %(rest)' |
  sort -k2 -n | tail -20

2. Switch to a shallow fetch and set a sane depth. For most CI you only need the tip commit(s). Set the Git strategy and depth in .gitlab-ci.yml (project or job level):

variables:
  GIT_STRATEGY: fetch     # reuse the cached working copy instead of full clone
  GIT_DEPTH: "20"         # shallow history; enough for most diffs/tags
  GIT_SUBMODULE_STRATEGY: none

Set GIT_DEPTH: "0" only when a job genuinely needs full history (e.g. git describe across many tags) — and pair it with the buffer/timeout tuning below.

3. Raise the HTTP post buffer and disable HTTP/2 if the errors are curl 92/18. Do this in a pre_get_sources_script (runs before the clone) or in the runner’s config:

default:
  hooks:
    pre_get_sources_script:
      - git config --global http.postBuffer 524288000   # 500 MB
      - git config --global http.version HTTP/1.1        # sidestep flaky HTTP/2 proxies
      - git config --global http.lowSpeedLimit 1000      # bytes/sec
      - git config --global http.lowSpeedTime 300        # allow slow transfers up to 5 min

4. Prefer the runner’s clone over HTTP proxies where possible. On the runner config (config.toml), reasonable clone settings and enough disk/memory matter:

[[runners]]
  # give large-repo jobs a generous per-job timeout at the runner level
  [runners.custom_build_dir]
    enabled = true
  [runners.cache]
    # cache clones between jobs to reduce transfer volume

If a reverse proxy fronts GitLab, raise its client/idle timeouts (e.g. Nginx proxy_read_timeout, client_max_body_size, and send_timeout) so long fetches aren’t cut.

5. For LFS failures, verify and stage LFS separately. Check whether LFS objects are the failing transfer:

git lfs env
git lfs ls-files | wc -l
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git lfs pull   # verbose to see where it drops

Consider GIT_LFS_SKIP_SMUDGE: "1" during clone and pulling LFS explicitly with retries in a script step.

6. Rule out memory. If you see fatal: early EOF / index-pack failed, index-pack may be OOM-killed. Check the runner:

dmesg -T | grep -i 'killed process'          # OOM killer evidence
free -h

Give the runner (or the job’s container) more memory, or reduce pack size via shallow fetch.

Example Root Cause Analysis

A backend repo with several years of history and a few large seed-data binaries started failing roughly one job in four with RPC failed; curl 92 HTTP/2 stream ... not closed cleanly followed by fatal: the remote end hung up unexpectedly. Small doc-only MRs always passed.

Diagnosis:

  1. The failure was always in “Getting source from Git repository,” never in scripts — so it was the fetch, not the build.
  2. du -sh .git reported 3.1 GB, and the largest-objects listing showed several 200 MB+ binaries committed years earlier still weighting every clone.
  3. The pipeline used the default GIT_STRATEGY: clone with no GIT_DEPTH, so every job re-cloned full history through a corporate reverse proxy that terminated HTTP/2 streams past ~60s.

Fix, applied in stages:

variables:
  GIT_STRATEGY: fetch
  GIT_DEPTH: "20"

default:
  hooks:
    pre_get_sources_script:
      - git config --global http.version HTTP/1.1
      - git config --global http.postBuffer 524288000

Switching to a shallow fetch cut the per-job transfer from ~3 GB to under 50 MB, and forcing HTTP/1.1 sidestepped the proxy’s HTTP/2 stream handling. The intermittent failures stopped. As a follow-up the team scheduled a history rewrite to move the large binaries to LFS, permanently shrinking the pack.

Prevention Best Practices

  • Default to GIT_STRATEGY: fetch with a small GIT_DEPTH (10–50); use full depth only for the specific jobs that need it.
  • Keep large binaries out of Git history — use Git LFS or an artifact registry — so every clone isn’t dragging dead weight.
  • Set http.postBuffer and http.version HTTP/1.1 via pre_get_sources_script on repos that transfer large packs behind strict proxies.
  • Raise reverse-proxy/load-balancer read and idle timeouts in front of GitLab so long transfers aren’t severed.
  • Ensure runners have enough memory for index-pack on your largest repo; watch for OOM kills.
  • Use GIT_LFS_SKIP_SMUDGE plus an explicit, retried git lfs pull step for LFS-heavy repos.
  • Add job-level retry: for the transient network class so a one-off drop self-heals: retry: { max: 2, when: [runner_system_failure, stuck_or_timeout_failure] }.

Quick Command Reference

# Measure repo weight and find the largest objects
du -sh .git && git count-objects -vH
git rev-list --objects --all | git cat-file --batch-check='%(objectsize) %(rest)' | sort -n | tail -20

# Tuning applied before the clone (pre_get_sources_script)
git config --global http.postBuffer 524288000
git config --global http.version HTTP/1.1
git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 300

# LFS diagnostics
git lfs env && git lfs ls-files | wc -l
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git lfs pull

# Look for OOM-killed index-pack on the runner host
dmesg -T | grep -i 'killed process'
free -h
# Shallow, resilient source-fetch defaults in .gitlab-ci.yml
variables:
  GIT_STRATEGY: fetch
  GIT_DEPTH: "20"
build:
  script: ["make build"]
  retry:
    max: 2
    when: [runner_system_failure, stuck_or_timeout_failure]

Conclusion

fatal: the remote end hung up unexpectedly in a GitLab job is a source-fetch failure, not a build bug: the Git transfer was cut mid-stream by size, buffer, timeout, memory, or proxy limits. The highest-leverage fixes are switching to a shallow GIT_STRATEGY: fetch with a modest GIT_DEPTH, raising http.postBuffer (and forcing HTTP/1.1 on flaky proxies), and keeping large binaries out of Git history via LFS. Add a targeted retry: for transient network drops, and clones become fast and reliable even on large repositories.

Free download · 368-page PDF

Fixed it? Get 500 GitLab CI/CD & DevOps AI prompts — free

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.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

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.