What's Actually Happening
GitHub Actions workflow cannot find artifacts. Download fails because artifact doesn't exist or is not accessible.
The Error You'll See
```yaml - uses: actions/download-artifact@v4
Error: Unable to find artifact named 'my-artifact' ```
Artifact expired:
Error: Artifact has expiredPermission denied:
Error: Not authorized to access artifactNo artifacts:
```yaml - uses: actions/download-artifact@v4
Error: No artifacts found for download ```
Why This Happens
- 1.Upload failed - Artifact not uploaded successfully
- 2.Wrong name - Artifact name mismatch
- 3.Different workflow - Artifact from different workflow run
- 4.Expired - Artifact retention period passed
- 5.Permissions - Insufficient permissions to access
- 6.No artifacts created - Job didn't create artifacts
Step 1: Check Artifact Upload
```yaml # Verify upload step in workflow:
- name: Upload Artifact
- uses: actions/upload-artifact@v4
- with:
- name: my-artifact
- path: dist/
- retention-days: 5
# Check job output: # In GitHub UI -> Actions -> Run -> Job -> Upload step # Should show: "Artifact my-artifact successfully uploaded"
# Verify upload path exists: - name: Check files run: | ls -la dist/
- name: Upload Artifact
- uses: actions/upload-artifact@v4
- with:
- name: my-artifact
- path: dist/
# Use if-no-files-found: - uses: actions/upload-artifact@v4 with: name: my-artifact path: dist/ if-no-files-found: error # or warn, ignore ```
Step 2: Check Artifact Name
```yaml # Upload artifact: - uses: actions/upload-artifact@v4 with: name: my-artifact # This name path: dist/
# Download artifact - must match: - uses: actions/download-artifact@v4 with: name: my-artifact # Must match exactly
# Case-sensitive! # my-artifact != My-Artifact
# Download all artifacts: - uses: actions/download-artifact@v4
# Download specific artifact: - uses: actions/download-artifact@v4 with: name: my-artifact
# Pattern matching: - uses: actions/download-artifact@v4 with: pattern: my-artifact-* path: artifacts/ ```
Step 3: Check Job Dependencies
```yaml # Jobs run in parallel by default # Download job must wait for upload job:
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm build - uses: actions/upload-artifact@v4 with: name: my-artifact path: dist/
deploy: needs: build # Wait for build job runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: my-artifact
# If needs missing, deploy runs before build completes ```
Step 4: Check Artifact Retention
```yaml # Default retention is 90 days # Shorter retention may expire:
- uses: actions/upload-artifact@v4
- with:
- name: my-artifact
- path: dist/
- retention-days: 1 # Only 1 day!
# Check retention in UI: # Actions -> Artifacts -> my-artifact # Shows expiration date
# Increase retention: - uses: actions/upload-artifact@v4 with: name: my-artifact path: dist/ retention-days: 30
# Repository settings also affect retention: # Settings -> Actions -> General -> Artifact and log retention ```
Step 5: Fix Cross-Workflow Access
```yaml # To download artifact from different workflow:
jobs: download: runs-on: ubuntu-latest steps: - name: Download artifact from another workflow uses: actions/download-artifact@v4 with: name: my-artifact github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }}
# Or use dawidd6/action-download-artifact: - uses: dawidd6/action-download-artifact@v3 with: workflow: build.yml name: my-artifact path: dist/ github_token: ${{ secrets.GITHUB_TOKEN }}
# For specific workflow run: - uses: dawidd6/action-download-artifact@v3 with: workflow: build.yml run_id: 1234567890 name: my-artifact ```
Step 6: Check Permissions
```yaml # Ensure proper permissions:
jobs: build: runs-on: ubuntu-latest permissions: contents: read actions: write # Needed to upload artifacts steps: - uses: actions/upload-artifact@v4
download: runs-on: ubuntu-latest permissions: contents: read actions: read # Needed to download artifacts steps: - uses: actions/download-artifact@v4
# At workflow level: permissions: contents: read actions: write
# For cross-repo access: - uses: actions/download-artifact@v4 with: name: my-artifact github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} repository: owner/repo run-id: 1234567890 ```
Step 7: Debug Artifact Issues
```yaml # List all artifacts in job:
- name: List artifacts
- env:
- GH_TOKEN: ${{ github.token }}
- run: |
- gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts
# Check artifact existence: - name: Check artifact run: | gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts \ --jq '.artifacts[] | select(.name == "my-artifact")'
# Debug download: - name: Download with debug uses: actions/download-artifact@v4 with: name: my-artifact continue-on-error: true
- name: List downloaded
- run: ls -la
# Check workflow run: - name: Get workflow run info run: | gh run view ${{ github.run_id }} --json artifacts ```
Step 8: Handle Matrix Builds
```yaml # Matrix build creates multiple artifacts:
jobs: build: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/upload-artifact@v4 with: name: artifact-${{ matrix.os }} path: dist/
# Download all matrix artifacts: - uses: actions/download-artifact@v4 with: pattern: artifact-* path: artifacts/ merge-multiple: true
# Or specific matrix artifact: - uses: actions/download-artifact@v4 with: name: artifact-ubuntu-latest ```
Step 9: Fix v4 Breaking Changes
```yaml # actions/upload-artifact@v4 and download-artifact@v4 changes:
# v3 behavior (implicit merge): - uses: actions/download-artifact@v3 # Downloads all artifacts to current directory
# v4 behavior (explicit): - uses: actions/download-artifact@v4 # Error if name not specified! with: name: my-artifact
# Download all in v4: - uses: actions/download-artifact@v4 # No name specified = download all
# Path changes in v4: - uses: actions/download-artifact@v4 with: name: my-artifact path: my-artifact/ # Creates directory # Files: my-artifact/file1, my-artifact/file2
# Merge multiple artifacts: - uses: actions/download-artifact@v4 with: pattern: artifact-* merge-multiple: true path: merged/ ```
Step 10: GitHub Actions Artifact Verification
```yaml # Add verification step to workflow:
- name: Verify Artifact
- if: always()
- run: |
- echo "=== Checking artifact upload ==="
# List all artifacts gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts \ --jq '.artifacts[] | "Name: \(.name), ID: \(.id), Size: \(.size_in_bytes)"'
echo "" echo "=== Checking artifact files ===" if [ -d "my-artifact" ]; then echo "Artifact directory exists" ls -la my-artifact/ else echo "ERROR: Artifact directory not found" fi env: GH_TOKEN: ${{ github.token }}
# Or create composite action: # .github/actions/check-artifact/action.yml name: 'Check Artifact' inputs: artifact-name: required: true runs: using: 'composite' steps: - shell: bash run: | gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts \ --jq '.artifacts[] | select(.name == "${{ inputs.artifact-name }}")' ```
GitHub Actions Artifact Checklist
| Check | Expected |
|---|---|
| Upload succeeded | "successfully uploaded" |
| Name matches | Same name upload/download |
| Job depends | needs: upload-job |
| Retention | Not expired |
| Permissions | actions: read/write |
| Path exists | Files uploaded |
Verify the Fix
```yaml # After fixing artifact issue
# 1. Check upload in workflow logs // Artifact uploaded successfully
# 2. Verify in UI # Actions -> Run -> Artifacts // Artifact listed
# 3. Download succeeds - uses: actions/download-artifact@v4 with: name: my-artifact // Files downloaded
# 4. Check file contents - run: ls -la my-artifact/ // Expected files present
# 5. Use in downstream job // Job succeeds with artifact
# 6. Test retention # Wait specified days // Artifact still available ```
Related Issues
- [Fix GitHub Actions Workflow Failed](/articles/fix-github-actions-workflow-failed)
- [Fix GitLab CI Pipeline Stuck](/articles/fix-gitlab-ci-pipeline-stuck)
- [Fix Jenkins Build Stuck](/articles/fix-jenkins-build-stuck)