How Long Are Artifacts Kept?

GitHub keeps workflow artifacts for 90 days by default. Public repositories allow 1 to 90 days, private repositories 1 to 400, and each upload can set less.

GitHub keeps workflow artifacts and log files for 90 days by default, and that period is a setting rather than a fixed rule: public repositories can move it anywhere from 1 to 90 days, private repositories from 1 to 400 days, and any single upload can ask for a shorter window with retention-days. Holding large artifacts for a long time is a storage bill decision rather than a technical one, because the artifact service charges for gigabytes held over time and the 90 day default multiplies every upload by 90.

Answer

The default and the ranges come from GitHub's organization retention documentation, checked on 2026-08-13. Four places can set the number, and the narrowest one wins.

Where it is setFieldAllowed rangeWhat it covers
RepositorySettings, Actions, General, Artifact and log retention1 to 90 days public, 1 to 400 days privateNew artifacts and logs in that repository
OrganizationOrganization settings, Actions, GeneralSame ranges, caps every repository below itNew artifacts and logs across the org
EnterpriseEnterprise policies for GitHub ActionsSame ranges, caps every organization below itNew artifacts and logs across the enterprise
A single uploadretention-days on actions/upload-artifact1 day up to the limit aboveThat one artifact

Two rules decide what actually happens. A retention change applies to new artifacts and log files only and never reaches back to existing objects, so lowering the repository default today leaves last week's 90 day uploads on their original schedule. And the per-upload value cannot exceed the repository, organization, or enterprise limit, per GitHub's store and share data tutorial, so retention-days is a way to shorten a window and never a way to extend one.

Splitting the window by artifact is the change worth making first, because one job usually produces outputs with very different shelf lives.

name: build
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-4x
    steps:
      - uses: actions/checkout@v4
      - run: make build
      - name: Upload failure triage bundle
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: debug-bundle
          path: build/debug
          retention-days: 3
      - name: Upload release binary
        uses: actions/upload-artifact@v4
        with:
          name: release-binary
          path: build/release/app
          retention-days: 30

The triage bundle exists for whoever reads the failed run that afternoon, so 3 days covers it. The binary might be pulled by a release job or a rollback, so it holds for 30. Neither one needs the 90 day default, and the run that produced them can be deleted outright, which removes its artifacts from storage with it, as the remove workflow artifacts documentation describes. The same page notes that the REST API returns an expires_at value per artifact, which is the fastest way to confirm what a repository is really holding.

Every rule above behaves identically on all four, because artifacts are stored by GitHub rather than by the runner.

Detail

The storage model behind the number

Retention cost follows one multiplication:

artifact size in GB x uploads per day x retention days = gigabytes held in steady state

GitHub bills shared storage for artifacts and GitHub Packages at $0.25 per GB-month beyond the allowance included with the plan, accruing from hourly usage, and its own worked example divides GB-hours by 744 hours per month (GitHub Actions billing, checked on 2026-08-13). Take a repository that uploads one 250 MB artifact on each of 40 runs a day.

Retention windowGigabytes held in steady stateMonthly storage at $0.25 per GB-month
1 day10$2.50
3 days30$7.50
7 days70$17.50
30 days300$75.00
90 days, the default900$225.00

Nothing about the workflow changed across those rows. The only variable is the number in the retention field, and it moves the monthly line by a factor of 90 from top to bottom. This is also why a cleanup pass feels slow to pay off: deleting artifacts frees space for current storage without reducing the usage already accrued in the current billing cycle, so the saving lands in the next cycle.

When rebuilding costs less than holding

For anything reproducible, retention competes with a rebuild, and both sides carry a rate.

  • One GB-day of artifact storage: $0.25 divided by the 744 hour month is $0.0081.
  • One 250 MB artifact held for a day: $0.0020. Held for the full 90 days: $0.18.
  • A 12 minute rebuild on warp-ubuntu-latest-x64-4x at $0.008 per minute from the pricing page, checked on 2026-08-13: $0.096.
  • Break-even: $0.096 divided by $0.0020 is about 48 days.

So a reproducible artifact that someone requests once a quarter is cheaper to rebuild than to keep past roughly seven weeks. The arithmetic inverts for anything you cannot reproduce on demand: signed binaries, outputs built from a base image tag that has since moved, or a build whose inputs are gone. Those belong in durable storage you control, with a retention policy you set, rather than in a 400 day artifact.

Pricing on the runner side of that sum is purely usage based.

For artifacts a later job needs, use a registry and a digest

A long retention window is the wrong container for an output that a deploy job consumes. Push the image once, capture the digest, and reference that digest downstream.

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-4x
    outputs:
      digest: ${{ steps.push.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

  deploy:
    needs: build
    runs-on: warp-ubuntu-latest-x64-2x
    steps:
      - run: |
          docker pull ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}

The digest pins the exact bytes the build produced, so the deploy job cannot pick up a retagged image. Registry layers are shared across builds, so a hundred runs of a mostly unchanged image store one copy of the common layers instead of a hundred full artifacts. And the retention question moves to a registry policy you write once. Build once, deploy many on GitHub Actions covers the full shape, and large artifact uploads on GitHub Actions covers the cases that stay in the artifact service.

Where retention shows up in your numbers

Artifact gigabytes appear on GitHub's billing surface, since GitHub stores them. What the WarpBuild side gives you is the other half of the break-even: the Jobs section of the Billing report reports duration percentiles per repository, workflow, and job name, so the rebuild minute count in the arithmetic above is a measured number rather than a guess.

Keep artifact retention separate from cache expiry when you tune either one. A cache entry under the WarpBuild caching documentation expires 7 days after its last use, and each restore resets that clock, so hot entries survive indefinitely. Artifacts have no such reset, which is exactly why the default sits so far out at 90 days. The vocabulary is pinned down in artifact retention.

Upload and download steps also spend wall-clock time inside the job, so trimming what you upload shortens runs as well as the storage line. Speeding up GitHub Actions covers that side of the same change.

Can I set a different retention period for each artifact in a workflow?

Yes. The retention-days input on actions/upload-artifact sets a window for that artifact alone, so a triage bundle can expire in 3 days while a release binary from the same job holds for 30. The value cannot exceed the limit set by the repository, organization, or enterprise, so the per-upload setting only ever shortens the window. GitHub documents the input in its store and share data tutorial.

Does lowering the repository retention period delete artifacts already stored?

No. A retention change applies to new artifacts and log files only and does not apply retroactively to existing objects. Remove the existing ones from the workflow run summary, the REST API, or gh run delete, all covered in the remove workflow artifacts documentation. GitHub also notes that deleting artifacts frees space for current storage without reducing the accrued usage already counted in the current billing cycle, so the new number shows up on the following invoice.

Do artifacts expire the same way as caches?

No. Artifact retention is a countdown from upload that nothing resets, so a 90 day artifact disappears 90 days after the run that produced it. A WarpBuild cache entry expires 7 days after its last use, and every restore restarts that window, so a busy cache entry never ages out. The two are separate meters with separate rates, described in the caching documentation and on the pricing page.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.