{"record":{"id":"59d18a4aad0d6c98","repo":"composer/composer","slug":"invalid-credentials-for-url-aborting","errorCode":null,"errorMessage":"Invalid credentials for '{url}', aborting.","messagePattern":"Invalid credentials for '(.+?)', aborting\\.","errorType":"http","errorClass":"TransportException","httpStatus":null,"severity":"error","filePath":"src/Composer/Util/AuthHelper.php","lineNumber":162,"sourceCode":"                        $message .= 'create a GitHub OAuth token to access private repos';\n                    }\n                }\n            }\n\n            if (!$gitHubUtil->authorizeOAuth($origin)\n                && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message))\n            ) {\n                throw new TransportException('Could not authenticate against '.$origin, 401);\n            }\n        } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {\n            $message = \"\\n\".'Could not fetch '.Url::sanitize($url).', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');\n            $gitLabUtil = new GitLab($this->io, $this->config, null);\n\n            $auth = null;\n            if ($this->io->hasAuthentication($origin)) {\n                $auth = $this->io->getAuthentication($origin);\n                if (in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], true)) {\n                    throw new TransportException(\"Invalid credentials for '\" . Url::sanitize($url) . \"', aborting.\", $statusCode);\n                }\n            }\n\n            if (!$gitLabUtil->authorizeOAuth($origin)\n                && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message))\n            ) {\n                throw new TransportException('Could not authenticate against '.$origin, 401);\n            }\n\n            if ($auth !== null && $this->io->hasAuthentication($origin)) {\n                if ($auth === $this->io->getAuthentication($origin)) {\n                    throw new TransportException(\"Invalid credentials for '\" . Url::sanitize($url) . \"', aborting.\", $statusCode);\n                }\n            }\n        } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') {\n            $askForOAuthToken = true;\n            $origin = 'bitbucket.org';\n            if ($this->io->hasAuthentication($origin)) {","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/composer/composer/blob/6ffc1177404d0c50119c22dde6564a380f4a82c9/src/Composer/Util/AuthHelper.php#L144-L180","documentation":"Thrown as a TransportException during GitLab authentication when the origin already has stored credentials whose password field is one of the legacy token types ('gitlab-ci-token', 'private-token', or 'oauth2'). Composer treats these as pre-validated tokens, so if the server still rejects the request (401/403) the credentials are stale/revoked and retrying the same value is pointless, so it aborts rather than loop. The {url} is URL-sanitized to avoid leaking any embedded credentials.","triggerScenarios":"Called from AuthHelper::promptAuthIfNeeded() for a host in config 'gitlab-domains', after the IO already has authentication for that origin, and $auth['password'] is 'gitlab-ci-token' | 'private-token' | 'oauth2'. Reached when GitLab returns 401/403 and the interactive/automatic OAuth flow was not triggered because a token-type credential is already configured.","commonSituations":"A GitLab personal access token or CI job token stored in auth.json or COMPOSER_AUTH that has since been revoked, expired, or lacks the read_api scope; rotating to a new GitLab instance hostname while keeping old credentials; using a CI token outside its job; domain added to gitlab-domains but token belongs to a different account.","solutions":["Regenerate a GitLab personal access token with at least read_api + read_repository scopes and re-run `composer config --global --auth http-basic.gitlab.example.com <user> <token>` (or set the oauth2/private-token entry correctly).","Clear the stale entry: `composer config --global --unset http-basic.<gitlab-host>` (and check auth.json) then let Composer prompt interactively.","If using a CI job token, ensure the pipeline actually provides a valid CI_JOB_TOKEN and that 'gitlab-ci-token' is intended for this origin; do not reuse job tokens across jobs/runs.","Verify the host is correctly listed in `gitlab-domains` and that the token was issued by that exact GitLab instance."],"exampleFix":"// before (stale token in auth.json)\n//   \"http-basic\": { \"gitlab.example.com\": { \"username\": \"oauth2\", \"password\": \"private-token\" } }\n// after - remove and re-auth\n//   composer config --global --unset http-basic.gitlab.example.com\n//   composer config --global gitlab-domains.gitlab.example.com token <NEW_PAT>","handlingStrategy":"try-catch","validationCode":"// Before downloading, confirm GitLab token validity and type\n$auth = $io->getAuthentication('gitlab.example.com');\nif ($auth !== null && in_array($auth['password'], ['gitlab-ci-token','private-token','oauth2'], true)) {\n    // Optionally ping /api/v4/user to confirm the token still works\n    // (do this in your own code; Composer itself trusts stored tokens)\n}","typeGuard":null,"tryCatchPattern":"try {\n    $downloader->get('https://gitlab.example.com/...');\n} catch (\\Composer\\Downloader\\TransportException $e) {\n    if (str_contains($e->getMessage(), 'Invalid credentials for')) {\n        // stale GitLab token: clear and surface a re-auth prompt\n        $config->getAuthConfigSource()->removeConfigSetting('http-basic.gitlab.example.com');\n    }\n    throw $e;\n}","preventionTips":["Store GitLab tokens via composer config rather than hard-coding so they can be rotated centrally.","Document token scopes (read_api, read_repository) required by your private repos in project README.","In CI, fail fast by validating GITLAB_TOKEN with a curl /api/v4/user call before composer install."],"tags":["authentication","gitlab","credentials","transport"],"analyzedSha":"6ffc1177404d0c50119c22dde6564a380f4a82c9","analyzedAt":"2026-08-07T00:01:08.491Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}