The problem: An artifact inventory job suddenly returned fewer records. Older workflow runs no longer showed artifacts in the GitHub UI, and the repository artifacts REST endpoint did not return the expired entries that the script previously expected.
The workflows had not stopped uploading artifacts. GitHub changed the display and API behavior on September 24, 2026: expired artifacts are no longer shown in run summaries or returned by the list-artifacts endpoints.
What changed
Previously, an expired artifact could remain visible with an Expired label even though its archive had already been deleted from storage. GitHub removed those stale entries to avoid suggesting that deleted files were still stored or billed.
The change affects:
- artifacts shown on the workflow-run summary;
- the repository list-artifacts REST endpoint;
- the workflow-run list-artifacts REST endpoint.
It does not change retention settings or billing. It also does not make an expired archive recoverable—the underlying files were already gone.
Confirm the workflow uploaded the artifact
Inspect the run logs before their own retention period expires:
gh run view RUN_ID --repo OWNER/REPO --log | rg -i 'upload-artifact|artifact name|retention'Or download the workflow-run logs through the REST API:
gh api \
-H 'X-GitHub-Api-Version: 2026-03-10' \
/repos/OWNER/REPO/actions/runs/RUN_ID/logs > run-logs.zipThe logs can establish that an upload step ran and record the artifact name. They cannot restore an archive whose retention period has already expired.
Fix the inventory design
Do not use the current artifact-list endpoint as a permanent historical ledger. Capture metadata while the artifact still exists:
- name: Upload test report
id: upload-report
uses: actions/upload-artifact@v4
with:
name: test-report-${{ github.run_id }}
path: reports/
retention-days: 30
- name: Record artifact manifest
shell: bash
run: |
jq -n \
--arg repository "$GITHUB_REPOSITORY" \
--arg run_id "$GITHUB_RUN_ID" \
--arg run_attempt "$GITHUB_RUN_ATTEMPT" \
--arg artifact_name "test-report-${GITHUB_RUN_ID}" \
--arg retention_days "30" \
'{repository:$repository,run_id:$run_id,run_attempt:$run_attempt,artifact_name:$artifact_name,retention_days:$retention_days}'Send that manifest to an approved durable system such as a release database, audit log destination, or object store with its own lifecycle policy. Do not retain build outputs indefinitely unless there is a real compliance or recovery requirement.
Check the effective retention policy
Repository, organization, and enterprise settings can constrain artifact retention. A workflow can also request a shorter period through retention-days, but it cannot exceed the upper limit set by the owning repository, organization, or enterprise.
Use the repository settings page or the retention API:
gh api \
-H 'X-GitHub-Api-Version: 2026-03-10' \
/repos/OWNER/REPO/actions/permissions/artifact-and-log-retentionGitHub's default is 90 days. Public repositories can configure 1–90 days, while private repositories can configure 1–400 days, subject to higher-level policy.
Changing the setting applies to new artifacts and logs, not retroactively to objects that already exist.
Verification
- Run a test workflow that uploads an artifact with a short, policy-compliant retention period.
- Confirm the active artifact appears in the run summary and REST response.
- Confirm the workflow emits or stores an independent metadata manifest.
- Update dashboards so missing expired records are treated as expected lifecycle behavior, not an upload failure.
- Alert on failed upload steps while the run is active instead of discovering gaps after expiration.
The fix in one sentence
I stopped treating GitHub's artifact list as permanent history, verified past uploads through retained run logs, and began recording artifact metadata at upload time.
Building reliable CI/CD pipelines with GitHub Actions? Explore this GitHub Actions course on Udemy. Affiliate link: DevOpsBoys may earn a commission at no extra cost to you.