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: 'Uploading artifacts ... too large' Artifact and Cache Failures

Fix GitLab CI 'artifacts too large archive' and 'Failed to extract cache' errors: Maximum artifacts size, artifacts:paths/exclude, cache keys, policy, and upload timeouts.

  • #gitlab-cicd
  • #troubleshooting
  • #errors
  • #artifacts

Overview

ERROR: Uploading artifacts ... too large archive and WARNING: ... Failed to extract cache are two different failures that get blamed on each other constantly. The first is GitLab Runner zipping the paths in your artifacts: block, handing the archive to the coordinator or object store, and being told the archive is bigger than the configured Maximum artifacts size. The second is the runner trying to download and unpack a cache: archive at the start of a job and finding nothing, a wrong key, or a corrupt zip. Both stem from how the runner moves files between jobs — but the fixes live in opposite halves of .gitlab-ci.yml.

You will see the artifact failure right after the script finishes, during the upload step:

Uploading artifacts...
dist/: found 3041 matching artifact files and directories
ERROR: Uploading artifacts as "archive" to coordinator... too large archive id=84213117 responseStatus=413 Request Entity Too Large
FATAL: too large

The cache failure shows up at the very start of a job, before your script runs, and is usually a warning rather than a hard error — which is exactly why a missing or corrupt cache silently degrades into a slow, “why is this rebuilding everything” job:

Restoring cache
Checking cache for node-modules-default-1...
WARNING: file does not exist
Failed to extract cache

The key distinction is direction and timing: artifacts are uploaded at the end and are size-capped by the instance/project Maximum artifacts size; cache is downloaded at the start and depends on a matching cache:key, a reachable distributed (S3/GCS) cache, and an intact archive. Knowing which one tripped — and whether it was a hard ERROR or a soft WARNING — is most of the battle.

Symptoms

  • The job log shows Uploading artifacts... followed by too large archive and responseStatus=413, and the job goes red even though the build succeeded.
  • A downstream job fails with dependency ... not found or missing dependency because the upstream artifact was never stored.
  • Every job log starts with WARNING: file does not exist / Failed to extract cache and then re-downloads or rebuilds everything from scratch.
  • Cache restore prints Checking cache for ... No URL provided, cache will not be downloaded from shared cache server. — the runner has no distributed cache configured.
  • Artifact upload stalls for minutes at Uploading artifacts... and ends in a 500, 502, or context deadline exceeded instead of a clean 201 Created.
# Inspect the failing job's upload tail and reported archive size
glab ci view --job 84213117 --log | grep -iE 'artifact|too large|responseStatus'
Uploading artifacts...
node_modules/: found 41122 matching artifact files and directories
build/: found 2904 matching artifact files and directories
ERROR: Uploading artifacts as "archive" to coordinator... too large archive id=84213117 responseStatus=413 Request Entity Too Large
# Confirm what the runner saw when it tried to restore cache
glab ci view --job 84213120 --log | grep -iE 'cache|extract|No URL'
Checking cache for node-modules-default-non_protected...
No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted.
WARNING: node-modules-default-non_protected: chmod cache.zip: no such file or directory
Failed to extract cache

Common Root Causes

1. The artifact archive exceeds the configured Maximum artifacts size

The most common artifact failure: the compressed archive is larger than the Maximum artifacts size set at the instance level (Admin Area > Settings > CI/CD) or overridden lower per project/group. The runner uploads, the coordinator returns 413 Request Entity Too Large, and the job fails after a green build.

build:
  script:
    - npm run build
  artifacts:
    paths:
      - build/      # 1.8 GB of output, over the configured cap
build/: found 2904 matching artifact files and directories
Uploading artifacts as "archive" to coordinator... too large archive id=84213117 responseStatus=413 Request Entity Too Large
FATAL: too large

The archive is simply over the configured limit. Shrink what you upload (only the deploy-relevant files), raise the per-project/instance Maximum artifacts size if you actually need it all, or push the heavy output to object storage / a package registry instead of an artifact. Setting a higher ARTIFACT_COMPRESSION_LEVEL (e.g. fastest -> default -> slow) can also drop a borderline archive under the cap.

2. artifacts:paths globbing sweeps in huge directories

A broad path like paths: ["."] or a parent directory pulls in node_modules/, .git/, vendor/, or local build caches that were never meant to be artifacts. The archive balloons from a few MB of real output to hundreds of MB, and you hit the cap or a glacial upload.

build:
  script:
    - npm ci && npm run build
  artifacts:
    paths:
      - .          # <- captures node_modules/, .git/, coverage/, everything
.: found 51877 matching artifact files and directories
Uploading artifacts as "archive" to coordinator...
WARNING: Uploading artifacts as "archive" to coordinator... too large archive id=84213118

The file count gives it away — tens of thousands of files means a directory you did not intend to ship. Narrow artifacts:paths to just the output (dist/, *.deb, target/release/app) and add artifacts:exclude for anything that slips in:

  artifacts:
    paths:
      - dist/
    exclude:
      - "**/node_modules/**"
      - ".git/**"

3. Artifact upload times out or fails to object storage

Even an in-limit archive can fail if the runner cannot reach the object store (S3/GCS/MinIO) the coordinator hands back, or the transfer stalls on a slow link. You get a 500/502, a context deadline exceeded, or a connection reset instead of 413, and the upload retries before giving up.

# Look for retry / network errors rather than a size rejection
glab ci view --job 84213119 --log | grep -iE 'retrying|deadline|reset|50[0-9]'
Uploading artifacts as "archive" to coordinator... ok id=84213119 responseStatus=500 Internal Server Error
WARNING: Retrying...                                error=invalid argument
Uploading artifacts as "archive" to coordinator... POST https://gitlab.example.com/api/v4/jobs/84213119/artifacts: 502
FATAL: invalid argument

This is infrastructure, not size. Confirm the object store is healthy and reachable from the runner, lower TRANSFER_METER_FREQUENCY to watch progress, raise ARTIFACT_UPLOAD_TIMEOUT, and rely on the runner’s built-in retries (ARTIFACT_DOWNLOAD_ATTEMPTS / RESTORE_CACHE_ATTEMPTS have upload equivalents). A smaller archive (causes 1 and 2) also makes a flaky link far more likely to succeed.

4. Cache key mismatch or the cache was never populated

cache: only helps if a previous job pushed an archive under the same key. If your cache:key changes every pipeline (e.g. keyed on ${CI_COMMIT_SHA}), or the first push job never ran, the restore finds nothing and prints file does not exist — a no-op, not a crash, but it means zero cache reuse.

test:
  cache:
    key: "$CI_COMMIT_SHA"   # <- unique per commit, never reused
    paths:
      - node_modules/
  script:
    - npm ci    # full install every time, cache always cold
Checking cache for $CI_COMMIT_SHA-non_protected...
WARNING: file does not exist
Failed to extract cache
$ npm ci
added 1442 packages in 38s

A per-commit key can never hit. Key the cache on the lockfile so it changes only when dependencies do, and add a CACHE_FALLBACK_KEY so a fresh branch can still warm-start from the default branch’s cache:

  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
variables:
  CACHE_FALLBACK_KEY: "node-modules-default"

5. Corrupted or partial cache archive fails extraction

If a job was killed mid-upload, the runner’s cache_dir filled up, or object storage returned a truncated object, the next job downloads a cache.zip it cannot read. This surfaces as a hard Failed to extract cache with an archive/read error rather than a clean “file does not exist”.

glab ci view --job 84213121 --log | grep -iE 'extract|cache.zip|archive'
Checking cache for node-modules-default-non_protected...
Downloading cache from https://storage.example.com/runner/cache/...  ok
WARNING: cache.zip: chmod cache.zip: no such file or directory
ERROR: Failed to extract cache
zip: not a valid zip file

A truncated or corrupt archive will keep failing until it is replaced. Clear the stale entry (bump the cache key suffix, e.g. -v2, or use the Clear runner caches button to invalidate all keys), confirm the runner’s cache_dir has free disk space, and let the next successful job re-push a clean archive. Caches are best-effort, so a single bad archive should degrade gracefully — but untracked: true plus a bad zip can make it noisy.

6. cache:policy misuse or local cache lost across runners

A job set to policy: pull only downloads the cache and never pushes, so nothing is ever stored for later jobs. Likewise, with local cache (no distributed S3/GCS backend), a cache pushed by a job on runner A is invisible to a job that lands on runner B — every restore is a miss because the archive only ever lived on one machine’s disk.

build:
  cache:
    key: deps
    paths: [vendor/]
    policy: pull        # <- only pulls, never pushes; cache stays empty
  script:
    - composer install
Checking cache for deps-non_protected...
No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted.
WARNING: file does not exist
Failed to extract cache

policy: pull with no companion policy: push (or pull-push) job means the cache is never written. Use pull-push on the job that builds dependencies and pull on jobs that only consume them, and configure a distributed cache ([runners.cache] with Type = "s3" / gcs in config.toml) so caches are shared across all runners instead of stranded on one host’s cache_dir.

Diagnostic Workflow

Step 1: Decide whether it is an artifact or a cache problem

glab ci view --job <JOB_ID> --log | grep -iE 'Uploading artifacts|Restoring cache|too large|Failed to extract'

Uploading artifacts ... too large at the end of the log is an artifact size/upload issue (causes 1-3). Failed to extract cache / No URL provided at the start is a cache issue (causes 4-6). They are unrelated subsystems — fix the right half of the file.

Step 2: For artifacts, read the file count and status code

glab ci view --job <JOB_ID> --log | grep -iE 'found [0-9]+ matching|responseStatus'

A huge found N matching artifact files count means a glob swept in a directory you did not intend (cause 2). responseStatus=413 is a hard size-cap rejection (cause 1); a 500/502/deadline is an upload/object-store failure (cause 3).

Step 3: For cache, inspect the key and whether a URL was provided

grep -nA6 'cache:' .gitlab-ci.yml
glab ci view --job <JOB_ID> --log | grep -iE 'Checking cache for|No URL provided'

If the key in the log changes every run (a SHA or timestamp), it can never hit (cause 4). No URL provided means there is no distributed cache, so caches do not cross runners (cause 6).

Step 4: Confirm what the runner actually stores and where

# Distributed cache + object store config on the runner
grep -iEA8 '\[runners.cache\]' /etc/gitlab-runner/config.toml
# Local cache dir disk usage (corruption / full disk -> cause 5)
df -h /var/lib/gitlab-runner; du -sh /var/lib/gitlab-runner/cache 2>/dev/null

A missing [runners.cache] block confirms local-only cache (cause 6). A full cache_dir or read errors point at a corrupt archive (cause 5).

Step 5: Reproduce the archive size locally

# What would actually get zipped, and how big?
git ls-files | wc -l
du -sh dist/ node_modules/ .git/ 2>/dev/null | sort -h

Sizing the directories your artifacts:paths would capture shows immediately whether you are shipping node_modules/ or .git/ by accident, and whether the real output is under the configured Maximum artifacts size.

Example Root Cause Analysis

The package job in a Node service started failing at the upload step, red every run, after a dependency refactor that had been green for months.

glab ci view --job 84213118 --log | grep -iE 'found [0-9]+ matching|too large|responseStatus'
.: found 51877 matching artifact files and directories
Uploading artifacts as "archive" to coordinator... too large archive id=84213118 responseStatus=413 Request Entity Too Large

51877 files for what should have been a handful of build outputs was the tell — something was sweeping in a directory it should not. Checking the artifacts: block:

grep -nA6 'artifacts:' .gitlab-ci.yml
12:  artifacts:
13:    paths:
14:      - .            # captures node_modules/, .git/, coverage/
15:    expire_in: 1 week

The refactor had changed paths: [dist/] to paths: [.] to “grab everything”, which now archived node_modules/ (41k files) and .git/ on top of the real dist/ output. The compressed archive blew past the project’s configured Maximum artifacts size and the coordinator rejected it with 413.

Fix: narrow the paths back to the actual output and exclude the heavy directories so a stray . can never balloon the archive again:

package:
  script:
    - npm ci && npm run build
  artifacts:
    paths:
      - dist/
    exclude:
      - "**/node_modules/**"
      - ".git/**"
    expire_in: 1 week

With paths: [dist/] and the excludes in place, the archive dropped from ~1.6 GB to ~22 MB, uploaded in seconds with a clean 201 Created, and downstream deploy jobs found their dependency again — instead of failing the whole pipeline on a 413 after a successful build.

Prevention Best Practices

  • Scope artifacts:paths to exactly the output you need (dist/, *.deb, target/release/app) and never . or a parent directory; add artifacts:exclude for node_modules/, .git/, and coverage dirs as a safety net.
  • Set expire_in: on artifacts and keep large build output in object storage or a package/container registry rather than as CI artifacts, so you stay well under the configured Maximum artifacts size.
  • Key cache: on lockfiles via cache:key:files, never on ${CI_COMMIT_SHA}, and set a CACHE_FALLBACK_KEY so fresh branches warm-start instead of doing a cold install every time.
  • Use a distributed cache ([runners.cache] S3/GCS in config.toml) so caches are shared across runners, and use policy: pull-push on the job that builds dependencies and policy: pull on jobs that only consume them. See more in the GitLab CI/CD guides.
  • Treat cache as best-effort: bump a key suffix (-v2) or clear runner caches when you see Failed to extract cache, and keep the runner cache_dir on a disk with free space so partial archives are not left behind.
  • Watch upload health with TRANSFER_METER_FREQUENCY, tune ARTIFACT_COMPRESSION_LEVEL for borderline archives, and lean on the runner’s retry attempts so a transient object-store 500 does not fail an otherwise-good job.

Quick Command Reference

# Artifact vs cache? (end-of-log upload vs start-of-log restore)
glab ci view --job <JOB_ID> --log \
  | grep -iE 'Uploading artifacts|Restoring cache|too large|Failed to extract'

# Artifact: file count (glob sweep) and status code (413 = size cap)
glab ci view --job <JOB_ID> --log | grep -iE 'found [0-9]+ matching|responseStatus'

# Cache: key in use and whether a distributed URL was provided
grep -nA6 'cache:' .gitlab-ci.yml
glab ci view --job <JOB_ID> --log | grep -iE 'Checking cache for|No URL provided'

# Runner-side: distributed cache config + local cache dir / disk
grep -iEA8 '\[runners.cache\]' /etc/gitlab-runner/config.toml
df -h /var/lib/gitlab-runner; du -sh /var/lib/gitlab-runner/cache

# Size what your artifacts:paths would actually capture
du -sh dist/ node_modules/ .git/ 2>/dev/null | sort -h

# Shrink a borderline archive at upload time
#   ARTIFACT_COMPRESSION_LEVEL: slow   (in variables:)
#   TRANSFER_METER_FREQUENCY: 5s       (watch upload progress)

Conclusion

Uploading artifacts ... too large archive and Failed to extract cache are two separate subsystems — uploads at the end, cache at the start — that get confused for each other. The usual root causes:

  1. The compressed archive exceeds the configured Maximum artifacts size and the coordinator returns 413 Request Entity Too Large.
  2. A broad artifacts:paths (like .) sweeps in node_modules/, .git/, or build caches and balloons the archive — fix with narrow paths and artifacts:exclude.
  3. The artifact upload to object storage times out or fails (500/502/deadline) because the object store is unreachable or the link stalls, not because of size.
  4. A cache:key that changes every run (e.g. ${CI_COMMIT_SHA}) or a push job that never ran means the cache is never populated, so restore is a no-op.
  5. A corrupted or partial cache.zip from a killed job or full cache_dir fails extraction until the entry is cleared and re-pushed.
  6. cache:policy: pull with no push job, or local-only cache across multiple runners, means the cache is never written or never shared.

Start by reading whether the failure is Uploading artifacts at the end or Failed to extract cache at the start, then the file count and status code — those facts route almost every one of these before you touch a config. For ad-hoc triage, the free incident assistant can turn a failing artifact or cache log into the likely root cause.

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.