pypa/pip · error · ValueError

Sorry, is a malformed VCS url. The format is + :// , e.g…

Error message

Sorry, {url!r} is a malformed VCS url. The format is <vcs>+<protocol>://<url>, e.g. svn+http://myrepo/svn/MyApp#egg=MyApp

What it means

Raised by VersionControl.get_url_rev_and_auth() when the URL scheme does not contain a '+' separator, meaning the VCS prefix (e.g. 'git+', 'svn+', 'hg+') is missing. The required format is <vcs>+<protocol>://<url>. Without the '+' pip cannot determine which VCS backend to use.

Solutions

  1. Add the correct VCS prefix to the URL: 'git+', 'svn+', 'hg+', or 'bzr+'
  2. Ensure the format is <vcs>+<protocol>://<url>, e.g. 'git+https://github.com/user/repo.git'
  3. For editable installs, prefix with '-e': 'pip install -e git+https://...'
  4. Add '#egg=<projectname>' if installing by project name from VCS

Example fix

// before
pip install https://github.com/user/repo.git
// after
pip install git+https://github.com/user/repo.git
# or for editable
pip install -e "git+https://github.com/user/repo.git#egg=myproject"
Defensive patterns

Strategy: validation

Validate before calling

import urllib.parse

SUPPORTED_VCS = ('git', 'svn', 'hg', 'bzr')

def is_valid_vcs_url(url: str) -> bool:
    scheme = urllib.parse.urlsplit(url).scheme
    if '+' not in scheme:
        return False
    vcs, _, proto = scheme.partition('+')
    return vcs in SUPPORTED_VCS and bool(proto)

# before installing
if not is_valid_vcs_url(url):
    raise ValueError(f'add a VCS prefix, e.g. git+{url}')

Type guard

import urllib.parse

def has_vcs_prefix(url: str) -> bool:
    return '+' in urllib.parse.urlsplit(url).scheme

Try / catch

try:
    url, rev, auth = vc.get_url_rev_and_auth(url)
except ValueError as e:
    if 'malformed VCS url' in str(e):
        raise ConfigError(f'prefix the URL with git+/svn+/hg+/bzr+: {e}')
    raise

Prevention

When it happens

Trigger: Calling get_url_rev_and_auth(url) on a URL whose urllib.parse.urlsplit scheme has no '+' character. Reached when pip processes any VCS requirement URL. For example, 'https://github.com/user/repo.git' without the 'git+' prefix.

Common situations: Forgetting to add the 'git+'/'svn+'/'hg+' prefix to a VCS requirement. A requirements.txt entry like '-e https://github.com/user/repo.git' instead of '-e git+https://...'. Copying a plain git URL from a browser into pip install.

Understand the failure class

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/1b6b9593776bd08c. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/vcs/versioncontrol.py:383

        information can be provided via the --username and --password options
        instead of through the URL.  For other subclasses like Git without
        such an option, auth information must stay in the URL.

        Returns: (netloc, (username, password)).
        """
        return netloc, (None, None)

    @classmethod
    def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]:
        """
        Parse the repository URL to use, and return the URL, revision,
        and auth info to use.

        Returns: (url, rev, (username, password)).
        """
        scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
        if "+" not in scheme:
            raise ValueError(
                f"Sorry, {url!r} is a malformed VCS url. "
                "The format is <vcs>+<protocol>://<url>, "
                "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
            )
        # Remove the vcs prefix.
        scheme = scheme.split("+", 1)[1]
        netloc, user_pass = cls.get_netloc_and_auth(netloc, scheme)
        rev = None
        if "@" in path:
            path, rev = path.rsplit("@", 1)
            if not rev:
                raise InstallationError(
                    f"The URL {url!r} has an empty revision (after @) "
                    "which is not supported. Include a revision after @ "
                    "or remove @ from the URL."
                )
            rev = urllib.parse.unquote(rev)
        url = urllib.parse.urlunsplit((scheme, netloc, path, query, ""))

View on GitHub (pinned to f399c37189)