Composer "Failed to download X (403)" rate limited in CI
Composer requested a package archive from GitHub and the response was 403, usually because the shared CI IP hit GitHub's rate limit while unauthenticated. An OAuth token lifts the limit; caching dist archives avoids re-downloading.
What this error means
composer install fails with "Failed to download vendor/pkg from dist: ... The \"https://api.github.com/repos/...\" file could not be downloaded (HTTP/1.1 403 Forbidden)" and a rate-limit hint.
Failed to download symfony/console from dist:
The "https://api.github.com/repos/symfony/console/zipball/abc123" file could not be
downloaded (HTTP/1.1 403 Forbidden)
It is possible that you are throttled by GitHub. Create an OAuth token to go over the rate limit.Common causes
Anonymous GitHub downloads were rate limited
Without a token, GitHub limits requests per IP; shared CI runners hit the cap and archive downloads return 403.
No dist cache, so every job re-downloads
A cold Composer cache fetches every archive again, multiplying API calls and the chance of throttling.
How to fix it
Authenticate to lift the rate limit
Provide a GitHub token via COMPOSER_AUTH so archive downloads use the authenticated, higher quota.
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.COMPOSER_GH_TOKEN }}"}}'Prefer dist and cache it
Install from dist archives and persist the Composer cache so repeat runs avoid GitHub entirely.
- uses: actions/cache@v4
with:
path: ~/.cache/composer
key: composer-${{ hashFiles('composer.lock') }}How to prevent it
- Always authenticate Composer to GitHub in CI.
- Cache
~/.cache/composerbetween runs to cut download volume. - Use
--prefer-distso archives, not full clones, are fetched.