Introduction
GitHub Actions artifacts provide temporary storage for files generated during workflow runs. Artifacts can be shared between jobs within the same workflow run, downloaded from the GitHub UI, or accessed via API. They have configurable retention periods (default 90 days for public repos, configurable for private repos) and are scoped to specific workflow runs.
When artifact downloads fail with 403 Forbidden or "artifact expired" errors, the root cause is typically one of three issues: the artifact's retention period has expired, the download is attempting to access an artifact from a different run without proper permissions, or the artifact name or run ID is incorrect. Understanding artifact scope and lifecycle is essential for troubleshooting these failures.
Artifacts are not permanent storageโthey're designed for intermediate build outputs, test results, and temporary files that need to persist between jobs or be downloadable after a workflow completes. For long-term storage, use GitHub Packages, releases, or external storage systems.
Symptoms
When GitHub Actions artifact downloads fail, you will observe these symptoms:
actions/download-artifactfails with HTTP 403 Forbidden- Error message states "Artifact has expired" or "Artifact not found"
- Download works from GitHub UI but fails in workflow
- Cross-workflow artifact access fails
- Artifacts uploaded in one run cannot be downloaded in another
- Migration from v3 to v4 of upload/download actions changed behavior
Common error messages:
```yaml # Error in workflow log: Error: Unable to download artifact 'build-output' Error: Resource not accessible by integration Error: Artifact 'build-output' not found for this workflow run
# API error: { "message": "Resource not accessible by integration", "documentation_url": "https://docs.github.com/rest/actions/artifacts" }
# Retention expired: Error: Artifact 'test-results' has expired and is no longer available ```
GitHub UI showing artifact status:
Artifacts (Run #12345)
- build-output (15 MB) - Expired
- test-results (2 MB) - Available
- coverage-report (5 MB) - ExpiredCommon Causes
Several factors cause GitHub Actions artifact download failures:
- 1.Retention period expired: Artifacts have limited lifespans. Once the retention period passes, artifacts are automatically deleted and cannot be recovered.
- 2.Wrong workflow run context: Attempting to download an artifact from a different run without specifying the correct run ID. By default, download-artifact looks for artifacts in the current run.
- 3.Insufficient permissions: Cross-workflow artifact access requires appropriate token permissions. The default
GITHUB_TOKENmay not have access to artifacts from other runs. - 4.Artifact name mismatch: The download step references a different artifact name than what was uploaded, especially common with matrix-generated names.
- 5.Upload failed silently: The upload step appeared to complete but actually failed, leaving no artifact to download.
- 6.v3 to v4 migration issues: The v4 actions changed behavior around artifact naming and cross-run access.
- 7.Artifact size limits exceeded: Artifacts larger than the allowed size may fail to upload or download.
- 8.Run ID not passed correctly: When accessing artifacts from a previous run, the run ID must be explicitly provided.
Step-by-Step Fix
Follow these steps to diagnose and resolve artifact download failures:
Step 1: Verify artifact exists and is not expired
Check the artifact status:
```bash # List artifacts for a specific run gh api repos/:owner/:repo/actions/runs/:run_id/artifacts --jq '.artifacts[] | {name, expired, expires_at, size_in_bytes}'
# Output: { "name": "build-output", "expired": false, "expires_at": "2026-04-15T00:00:00Z", "size_in_bytes": 15728640 }
# Check if expired gh api repos/:owner/:repo/actions/runs/:run_id/artifacts --jq '.artifacts[] | select(.expired == true) | .name'
# Via GitHub UI: # Actions > [workflow run] > Artifacts section ```
Step 2: Verify correct artifact name
Check the upload matches the download:
```yaml # Upload step (producer) - name: Upload build output uses: actions/upload-artifact@v4 with: name: build-output # This name must match exactly path: dist/
# Download step (consumer) - name: Download build output uses: actions/download-artifact@v4 with: name: build-output # Must match upload name exactly ```
Common naming mistakes:
```yaml # WRONG: Case mismatch upload: name: Build-Output download: name: build-output # Case doesn't match!
# WRONG: Matrix name not included upload: name: build-${{ matrix.os }} download: name: build # Missing matrix suffix! ```
Step 3: For cross-run access, specify run ID
When downloading from a different run:
```yaml # Get the run ID from a previous workflow - name: Get latest run ID id: run-id run: | RUN_ID=$(gh api repos/:owner/:repo/actions/workflows/build.yml/runs \ --jq '.workflow_runs | sort_by(.created_at) | reverse | .[0].id') echo "run-id=$RUN_ID" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download artifact from previous run
- uses: actions/download-artifact@v4
- with:
- name: build-output
- run-id: ${{ steps.run-id.outputs.run-id }}
- github-token: ${{ secrets.GITHUB_TOKEN }}
`
Step 4: Configure retention appropriately
Set retention for your use case:
```yaml # Default retention (90 days for public repos) - uses: actions/upload-artifact@v4 with: name: build-output path: dist/
# Custom retention - uses: actions/upload-artifact@v4 with: name: build-output path: dist/ retention-days: 30 # Override default
# Short retention for temporary files - uses: actions/upload-artifact@v4 with: name: temp-files path: temp/ retention-days: 1 # Delete after 1 day ```
Step 5: Check permissions for cross-workflow access
Ensure token has required permissions:
```yaml jobs: download: runs-on: ubuntu-latest permissions: actions: read # Required to download artifacts contents: read steps: - uses: actions/download-artifact@v4 with: name: build-output run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }}
# For cross-repo access, use a PAT or GitHub App token - uses: actions/download-artifact@v4 with: name: build-output run-id: 12345 github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} ```
Step 6: Handle matrix artifacts
Deal with matrix-generated artifact names:
```yaml # Upload from matrix job jobs: build: strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/upload-artifact@v4 with: name: build-${{ matrix.os }} # Unique name per matrix item path: dist/
# Download all matrix artifacts jobs: combine: needs: build steps: - uses: actions/download-artifact@v4 with: pattern: build-* # Download all matching artifacts path: artifacts merge-multiple: true # Merge into single directory ```
Step 7: Verify upload succeeded
Check upload completed successfully:
```yaml - name: Upload with verification id: upload uses: actions/upload-artifact@v4 with: name: build-output path: dist/
- name: Verify upload
- run: |
- echo "Artifact ID: ${{ steps.upload.outputs.artifact-id }}"
- echo "Artifact URL: ${{ steps.upload.outputs.artifact-url }}"
# Verify files exist locally ls -la dist/
- name: Verify artifact exists
- run: |
- gh api repos/:owner/:repo/actions/runs/${{ github.run_id }}/artifacts \
- --jq '.artifacts[] | select(.name == "build-output") | .name'
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
`
Step 8: Handle v3 to v4 migration
Address breaking changes between versions:
```yaml # v3 behavior (deprecated) - uses: actions/download-artifact@v3 with: name: artifact-name
# v4 behavior changes: # 1. Cross-run downloads require explicit run-id # 2. Pattern matching uses different syntax # 3. Merge behavior changed
# v4 for same-run download (no changes needed) - uses: actions/download-artifact@v4 with: name: artifact-name
# v4 for cross-run download (requires run-id) - uses: actions/download-artifact@v4 with: name: artifact-name run-id: ${{ github.event.workflow_run.id }} ```
Verification
After fixing artifact issues, verify downloads work:
```yaml - name: Download artifact uses: actions/download-artifact@v4 with: name: build-output
- name: Verify download
- run: |
- echo "Downloaded files:"
- ls -la build-output/
if [ -z "$(ls -A build-output/)" ]; then echo "ERROR: Artifact directory is empty" exit 1 fi
echo "Artifact download successful" ```
Verify via API:
```bash # Check artifact exists and is downloadable gh api repos/:owner/:repo/actions/runs/:run_id/artifacts \ --jq '.artifacts[] | {name, expired, size_in_bytes}'
# Download via API (for testing) gh api repos/:owner/:repo/actions/artifacts/:artifact_id/zip \ -H "Accept: application/vnd.github+json" > artifact.zip
unzip artifact.zip ls -la ```
Prevention
To prevent artifact download failures:
- 1.Set appropriate retention: Configure retention based on access patterns.
```yaml # Short retention for CI artifacts retention-days: 7
# Longer retention for release artifacts retention-days: 90 ```
- 1.Use consistent artifact naming: Document naming conventions.
# Naming convention:
# build-{os}-{arch} - Build artifacts
# test-results-{suite} - Test outputs
# coverage-report - Coverage data- 1.Pass run IDs explicitly: Don't assume same-run context.
- uses: actions/download-artifact@v4
with:
name: build-output
run-id: ${{ needs.build.outputs.run-id || github.run_id }}- 1.Add verification steps: Confirm uploads succeeded.
- name: Verify artifact upload
if: always()
run: |
gh api repos/:owner/:repo/actions/runs/${{ github.run_id }}/artifacts \
--jq '.artifacts | length'- 1.Document artifact dependencies: Track which artifacts are needed where.
## Artifact Dependencies
- deploy.yml requires: build-output (from build.yml)
- test-report.yml requires: test-results (from test.yml)
- Retention: 30 days for all artifacts- 1.Use workflow_run event carefully: Handle cross-workflow access properly.
```yaml on: workflow_run: workflows: ["Build"] types: [completed]
jobs: download: steps: - uses: actions/download-artifact@v4 with: run-id: ${{ github.event.workflow_run.id }} name: build-output ```
Related Articles
- [WordPress troubleshooting: Fix IAM Access Denied 403 - Complete Tro](fix-iam-access-denied-403)
- [Fix Github Actions Artifact Upload Failed File Too Large 5gb Limit Issue in GitHub Actions](github-actions-artifact-upload-failed-file-too-large-5gb-limit)
- [Fix Github Actions Aws S3 Deploy Credentials Expired Rotate Issue in GitHub Actions](github-actions-aws-s3-deploy-credentials-expired-rotate)
- [Fix Github Actions Cache Evicted Manually Workflow Fails Download Issue in GitHub Actions](github-actions-cache-evicted-manually-workflow-fails-download)
- [Fix Github Actions Cache Miss Exact Hash Key Fallback Not Configured Issue in GitHub Actions](github-actions-cache-miss-exact-hash-key-fallback-not-configured)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "GitHub Actions Artifact Expired or 403 Download Failed", "description": "Resolve GitHub Actions artifact download failures by checking retention, artifact scope, run IDs, and token permissions for cross-workflow access.", "url": "https://www.fixwikihub.com/github-actions-artifact-expired-403-download-failed", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-22T14:08:18.542Z", "dateModified": "2026-01-22T14:08:18.542Z" } </script>