Composer Private Packagist "403 Forbidden" in CI
A 403 from Private Packagist means the request authenticated but the credentials are not permitted to read that package or repository. Unlike a 401 (no valid auth) or a masked 404, a 403 will not be fixed by retrying: the token needs the right access.
What this error means
composer install fails with a TransportException "could not be downloaded (HTTP/1.1 403 Forbidden)" for a repo.packagist.com URL while public packages install fine.
[Composer\Downloader\TransportException]
The "https://repo.packagist.com/acme/packages.json" file could not be
downloaded (HTTP/1.1 403 Forbidden)Common causes
The token lacks access to the Private Packagist org
Authentication succeeded but the token is not authorized for that organization or package feed.
Auth configured for the wrong host
A github-oauth entry was set instead of an http-basic entry for the Private Packagist host, so the correct credentials never reach it.
How to fix it
Configure http-basic auth for the Packagist host
- Store the Private Packagist token as a secret.
- Add it as an http-basic entry for
repo.packagist.cominCOMPOSER_AUTH. - Confirm the token has read access to the org for a persistent 403.
- run: composer install --no-interaction
env:
COMPOSER_AUTH: '{"http-basic":{"repo.packagist.com":{"username":"token","password":"${{ secrets.PACKAGIST_TOKEN }}"}}}'Verify token scope for a persistent 403
A 403 that survives correct auth config means the token principal lacks read access. Grant access in Private Packagist rather than retrying.
How to prevent it
- Use http-basic auth for the Private Packagist host, not github-oauth.
- Keep the Packagist token in CI secrets with least-privilege read scope.
- Rotate the token centrally and update the one secret.