Fix: GitHub Actions Artifacts Not Working — Upload Fails, Download Empty, or Artifact Not Found
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix GitHub Actions artifact issues — upload-artifact path patterns, download-artifact across jobs, retention days, artifact name conflicts, and the v3 to v4 migration.
The Problem
upload-artifact completes but the artifact is empty or not found:
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
# "Warning: No files were found with the provided path: dist/"Or download-artifact in a later job fails:
download-job:
needs: build-job
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
# Error: Unable to find artifact 'build-output'Or artifact names conflict and overwrite each other in a matrix build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/upload-artifact@v4
with:
name: test-results # Same name for all matrix jobs — conflict
path: results/Why This Happens
Artifacts in GitHub Actions have several non-obvious behaviors that catch even experienced CI engineers off guard. The actions look simple on the surface, but the upload and download steps are thin wrappers over a REST API with strict rules about scope, naming, and timing.
Paths are always resolved relative to the runner workspace. When you write path: dist/, the action looks at $GITHUB_WORKSPACE/dist/, not the directory your previous run: step was sitting in. A cd subdir && build step that produces files in subdir/dist/ will produce zero matches if the upload step asks for dist/. The action then emits a warning (not an error) and the next job downloads an empty artifact, which usually surfaces as a confusing failure several steps later.
Artifacts are also scoped to a workflow run. A job in run number 47 cannot download an artifact uploaded by run number 46 unless you explicitly cross runs with the run-id and github-token parameters. Inside a single run, the uploading job must finish before the downloading job starts — so the downloading job needs needs: declared, otherwise it may attempt the download before anything has been written.
Matrix jobs add another sharp edge: each matrix combination is treated as an independent job, but they share the artifact namespace within the run. Two jobs uploading to test-results will collide. In v3 the second upload silently overwrote the first; in v4 it fails outright unless overwrite: true is set, which is usually not what you want either.
Finally, the v3 to v4 transition broke several patterns that worked for years. Downloads without a name no longer flatten into the current directory, the pattern parameter replaced wildcards in name, and the underlying storage backend changed in ways that affect retention, deduplication, and immutability.
Version History: Artifact Action Evolution
The behavior of actions/upload-artifact and actions/download-artifact has shifted significantly across major versions. Knowing which version you are on changes the fix.
- v1 (2019, deprecated 2022) — original release. Used a different storage backend and was retired when v3 reached general availability. Workflows still pinned to
@v1started failing once GitHub disabled the API endpoint. - v2 (mid-2020 to late 2022) — added glob patterns, retention controls, and
if-no-files-found. Sunset alongside v3’s stabilization. - v3 (November 2022) — default retention became 90 days for public repos and configurable per organization for private repos. Download-all behavior flattened files into the working directory and silently overwrote duplicates. Many “the deploy job overwrote the build job’s files” incidents trace back to v3.
- v4 (December 2023) — major breaking release. Uploads are now immutable: you cannot append to an existing artifact, and duplicate names within a run fail by default. The download step creates a subdirectory per artifact when no
nameis given. Downloads are 10x faster on average because the action uses a new content-addressable backend. Thepatternandmerge-multipleparameters replaced wildcardnamematching. - v4.1+ (2024) — added cross-run download via
run-idandrepository, plus better error messages for the immutability rule.
If your workflow was authored before December 2023 and was bumped to @v4 without testing, the most common breakages are duplicate-name failures in matrix jobs, downloads ending up in unexpected subdirectories, and retries failing because the action refuses to overwrite. Pin to @v3 if you need the legacy behavior temporarily, but plan a migration — v3 is on the GitHub deprecation track.
Fix 1: Verify the Upload Path Exists
The most common cause of empty artifacts is the path not existing at upload time:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build # Outputs to dist/
# Debug: list what's in the workspace
- name: List files
run: ls -la dist/ || echo "dist/ does not exist"
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
if-no-files-found: error # Fail the step if nothing matched (default: warn)Common path issues:
# WRONG — path inside a subdirectory, but workspace is the root
- run: cd myapp && npm run build # Output goes to myapp/dist/
- uses: actions/upload-artifact@v4
with:
path: dist/ # Looks for $GITHUB_WORKSPACE/dist/ — wrong!
# CORRECT — include the subdirectory in the path
- uses: actions/upload-artifact@v4
with:
path: myapp/dist/
# Or set working-directory for the build step:
- name: Build
working-directory: myapp
run: npm run build
- uses: actions/upload-artifact@v4
with:
path: myapp/dist/ # Still relative to GITHUB_WORKSPACEUse glob patterns to capture multiple files:
- uses: actions/upload-artifact@v4
with:
name: test-results
path: |
coverage/
test-results/*.xml
**/*.log
if-no-files-found: errorFix 2: Download Artifacts Correctly Across Jobs
Artifacts are only available after the uploading job completes. Use needs: to declare the dependency:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
runs-on: ubuntu-latest
needs: build # Waits for 'build' to complete
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/ # Downloads to this directory
- name: Deploy
run: |
ls dist/ # Verify download succeeded
./deploy.shDownload all artifacts from the run:
# actions/download-artifact@v4 — download all artifacts
- uses: actions/download-artifact@v4
# No 'name' parameter — downloads all artifacts
# Each artifact goes into a subdirectory named after the artifact
- name: List downloaded artifacts
run: ls -la # Shows: build-output/, test-results/, etc.Download an artifact from a different workflow run:
- uses: actions/download-artifact@v4
with:
name: build-output
run-id: ${{ github.event.workflow_run.id }} # Specific run ID
github-token: ${{ secrets.GITHUB_TOKEN }}Fix 3: Fix Matrix Build Artifact Name Conflicts
In matrix builds, each job must upload to a unique artifact name:
# WRONG — all matrix jobs overwrite the same artifact
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- run: npm test
- uses: actions/upload-artifact@v4
with:
name: test-results # Same name — conflict in v4, overwrite in v3
path: results/
# CORRECT — unique name per matrix combination
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- run: npm test
- uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }} # Unique per job
path: results/
merge-results:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: test-results-* # Download all matching artifacts
merge-multiple: true # Merge into a single directory
path: all-results/Fix 4: Migrate from v3 to v4
actions/upload-artifact@v4 and actions/download-artifact@v4 have breaking changes from v3:
# v3 — download all artifacts merges into current directory
- uses: actions/download-artifact@v3
# Downloads all artifacts, potentially overwriting files with same names
# v4 — download all artifacts creates subdirectories
- uses: actions/download-artifact@v4
# Each artifact in its own subdirectory named after the artifact
# v3 — artifact names could be duplicated across matrix jobs (last writer wins)
# v4 — duplicate artifact names fail by default
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: output/
overwrite: true # Allow overwriting — restores v3 behaviorKey v4 changes:
# v4: 'path' when downloading now specifies the destination directory
- uses: actions/download-artifact@v4
with:
name: build-output
path: downloaded/ # Files go into downloaded/
# v4: use 'pattern' for wildcard matching (replaces downloading without 'name')
- uses: actions/download-artifact@v4
with:
pattern: build-* # Match multiple artifact names
merge-multiple: false # Each artifact in its own subdirectory (default)Fix 5: Set Retention and Size Limits
Artifacts have default retention and size limits that cause unexpected expiry or upload failures:
- uses: actions/upload-artifact@v4
with:
name: large-build
path: dist/
retention-days: 7 # Keep for 7 days (default: 90 for public, varies for private)
compression-level: 9 # 0-9, higher = smaller file, slower upload (default: 6)Size limits by plan:
- Free: 500 MB per artifact, 1 GB total per run
- Pro/Team: 2 GB per artifact
- Enterprise: configurable
Reduce artifact size:
# Exclude unnecessary files
- uses: actions/upload-artifact@v4
with:
name: build-output
path: |
dist/
!dist/**/*.map # Exclude source maps
!dist/**/*.test.* # Exclude test files
compression-level: 9Fix 6: Use Artifacts for Job Communication
Artifacts are the standard way to pass data between jobs in a workflow:
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
MATRIX=$(cat config/test-matrix.json)
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 1 # Short-lived — only needed for this run
test:
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: npm test -- --shard=${{ matrix.shard }}/4
- uses: actions/upload-artifact@v4
with:
name: test-results-shard-${{ matrix.shard }}
path: test-results/
if-no-files-found: error
report:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: test-results-shard-*
merge-multiple: true
path: all-test-results/
- name: Generate report
run: npx junit-merge all-test-results/**/*.xml -o combined.xml
- uses: actions/upload-artifact@v4
with:
name: test-report
path: combined.xml
retention-days: 30Still Not Working?
Artifact not found in a workflow_run event — when a triggered workflow tries to download artifacts from the parent workflow using the workflow_run event, you need to pass github-token and run-id explicitly. The artifact is only accessible after the parent workflow completes.
“Artifact storage quota has been reached” — your repository or organization has hit the artifact storage limit. Reduce retention-days for existing artifacts, delete old artifacts via the GitHub API (DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}), or increase the retention policy for your plan. Set short retention-days on temporary artifacts to avoid accumulation.
Artifact upload succeeds but shows 0 bytes — this happens when the path matches a directory structure but the files inside are empty, or when a previous step’s output was to stderr (and the files weren’t created). Add a run: ls -la <path> step before the upload to confirm files exist and have content.
Windows path separator issues — on Windows runners, use forward slashes in path: values even though Windows uses backslashes. GitHub Actions normalizes paths, but mixing separators can cause matching failures:
# Works on all platforms
path: build/output/ # Use forward slashes
# Avoid
path: build\output\ # May not work on Linux/macOS agentsRe-run of a single job uploads to a “new” artifact — when you re-run a failed job in v4, GitHub treats the new attempt as a separate upload context. If the original attempt already created an artifact with the same name, the rerun fails with a duplicate-name error. Either include ${{ github.run_attempt }} in the artifact name, or set overwrite: true on the upload step so the rerun replaces the previous attempt’s artifact.
hashFiles() cache key mismatches after a v4 download — v4 places downloaded files under a subdirectory by default (e.g., dist/dist/index.js instead of dist/index.js). Cache keys built from hashFiles('dist/**') will compute over a different file tree than they did under v3. Set path: on the download step to the exact destination, and verify with ls -R before computing any hash.
Composite actions can’t see uploaded artifacts directly — inside a composite action, actions/upload-artifact runs in the calling workflow’s context, but the working directory is the action’s checkout, not the caller’s workspace. If your composite action uploads files, use absolute paths anchored at ${{ github.workspace }} so the upload resolves correctly regardless of which workflow invokes the action.
For related CI/CD issues, see Fix: GitHub Actions Cache Not Working, Fix: GitHub Actions Timeout, Fix: GitHub Actions Process Completed with Exit Code 1, and Fix: GitHub Actions Permission Denied.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: GitHub Actions Secret Not Available — Environment Variable Empty in Workflow
How to fix GitHub Actions secrets that appear empty or undefined in workflows — secret scope, fork PR restrictions, environment protection rules, secret names, and OIDC alternatives.
Fix: GitHub Actions Matrix Strategy Not Working — Jobs Not Running or Failing
How to fix GitHub Actions matrix strategy issues — matrix expansion, include/exclude patterns, failing fast, matrix variable access, and dependent jobs with matrix outputs.
Fix: GitHub Actions Docker Build and Push Failing
How to fix GitHub Actions Docker build and push errors — registry authentication, image tagging, layer caching, multi-platform builds, and GHCR vs Docker Hub setup.
Fix: GitHub Actions Environment Variables Not Available Between Steps
How to fix GitHub Actions env vars and outputs not persisting between steps — GITHUB_ENV, GITHUB_OUTPUT, job outputs, and why echo >> $GITHUB_ENV is required.