composer/composer · error · UnexpectedValueException

Invalid url given for Composer repository: {url}

Error message

Invalid url given for Composer repository: {url}

What it means

After normalising the url, ComposerRepository parses it with parse_url() and requires a valid scheme. If parse_url returns false or there is no scheme, it throws UnexpectedValueException because it cannot decide how to fetch metadata. The url in the message is sanitised to avoid leaking credentials.

Source

Thrown at src/Composer/Repository/ComposerRepository.php:184

                // it is a local path, add file scheme
                $repoConfig['url'] = 'file://'.$localFilePath;
            } else {
                // otherwise, assume http as the default protocol
                $repoConfig['url'] = 'http://'.$repoConfig['url'];
            }
        }
        $repoConfig['url'] = rtrim($repoConfig['url'], '/');
        if ($repoConfig['url'] === '') {
            throw new \InvalidArgumentException('The repository url must not be an empty string');
        }

        if (str_starts_with($repoConfig['url'], 'https?')) {
            $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6);
        }

        $urlBits = parse_url(strtr($repoConfig['url'], '\\', '/'));
        if ($urlBits === false || empty($urlBits['scheme'])) {
            throw new \UnexpectedValueException('Invalid url given for Composer repository: '.Url::sanitize($repoConfig['url']));
        }

        if (!isset($repoConfig['options'])) {
            $repoConfig['options'] = [];
        }
        if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) {
            $this->allowSslDowngrade = true;
        }

        $this->options = $repoConfig['options'];
        $this->url = $repoConfig['url'];

        // force url for packagist.org to repo.packagist.org
        if (Preg::isMatch('{^(?P<proto>https?)://packagist\.org/?$}i', $this->url, $match)) {
            $this->url = $match['proto'].'://repo.packagist.org';
        }

        $baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');

View on GitHub (pinned to 6ffc117740)

Solutions

  1. Specify a full url with an explicit scheme, e.g. https://repo.example.com.
  2. For local paths use an absolute filesystem path or a file:// URL.
  3. Remove stray backslashes or encoded characters from the url.

Example fix

// before
{ "type": "composer", "url": "repo.example.com\\packages.json" }

// after
{ "type": "composer", "url": "https://repo.example.com/packages.json" }
Defensive patterns

Strategy: validation

Validate before calling

$url = $config['url'] ?? '';
if (parse_url((string) $url) === false || empty(parse_url((string) $url)['scheme'])) {
    throw new \InvalidArgumentException("Repository url has no valid scheme: {$url}");
}

Type guard

function hasValidScheme(string $url): bool {
    $bits = parse_url(strtr($url, '\\', '/'));
    return $bits !== false && !empty($bits['scheme']);
}

Prevention

When it happens

Trigger: A repository url that, after scheme-prefixing and slash-trimming, still has no parseable scheme — e.g. a url consisting only of a path fragment, or one containing characters that make parse_url fail. This typically happens when the input lacks a protocol and realpath() also fails (so the http:// default isn't added cleanly).

Common situations: Urls with backslashes or malformed characters; urls that are relative paths which don't resolve on disk; copy-paste of a url missing its protocol in an unusual form; typos like 'htps://'.

Related errors


AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07). Data as JSON: /api/errors/58cdd36d5bc23754. Report an issue: GitHub.