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
- Add the correct VCS prefix to the URL: 'git+', 'svn+', 'hg+', or 'bzr+'
- Ensure the format is <vcs>+<protocol>://<url>, e.g. 'git+https://github.com/user/repo.git'
- For editable installs, prefix with '-e': 'pip install -e git+https://...'
- 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
- Always prefix VCS URLs: git+, svn+, hg+, bzr+
- Use the format <vcs>+<protocol>://<url>
- Validate requirement URLs in requirements.txt before CI runs
- Add '#egg=<name>' for VCS-based editable installs
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- The URL has an empty revision (after @) which is not…
- {url}
- Editable requirements are not allowed as constraints
- Absolute paths are not supported in pylock files obtained…
- Badly formatted data
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)