Skip to content
Latchkey

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
  [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

  1. Store the Private Packagist token as a secret.
  2. Add it as an http-basic entry for repo.packagist.com in COMPOSER_AUTH.
  3. Confirm the token has read access to the org for a persistent 403.
.github/workflows/ci.yml
- 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.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →