pypa/pip · error · ValueError
Sorry, {url!r} is a malformed VCS url. The format is <vcs>+<
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 `VCSTool.get_url_rev_and_auth` when a URL passed to pip as a VCS requirement has no `+` in its URL scheme. pip requires the explicit `<vcs>+<protocol>://` form (e.g. `svn+https://`, `git+ssh://`) so it can identify which backend to dispatch to. A bare `https://...` or `svn://...` scheme is ambiguous and is rejected before any network operation.
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 d7d0d0a394)
Solutions
- Rewrite the URL to the `<vcs>+<protocol>://<host>/<path>` form, e.g. `git+https://github.com/org/repo.git`.
- Add the VCS prefix matching the protocol you actually use: `git+ssh://`, `svn+https://`, `hg+https://`, `bzr+http://`.
- If installing from a plain tarball/wheel, don't route it through VCS handling — give pip the direct archive URL instead.
Example fix
# before pip install svn://myrepo/svn/MyApp # after pip install svn+https://myrepo/svn/MyApp#egg=MyApp
Defensive patterns
Strategy: validation
Validate before calling
import urllib.parse
def is_valid_vcs_url(url: str) -> bool:
scheme = urllib.parse.urlsplit(url).scheme
return "+" in scheme and scheme.split("+", 1)[0] in {"git", "svn", "hg", "bzr"}
# before pip install:
assert is_valid_vcs_url(req_url), f"{req_url!r} is not a valid VCS URL" Type guard
def is_vcs_url(url: str) -> bool:
s = urllib.parse.urlsplit(url).scheme
return bool(s) and "+" in s Try / catch
null
Prevention
- Always write VCS install URLs as `<vcs>+<protocol>://...`.
- Lint requirements files for the `<vcs>+` prefix before committing.
- Generate VCS URLs from a helper that enforces the `+` scheme.
When it happens
Trigger: Pip resolves a requirement link whose `urlsplit(url).scheme` contains no `+` character — e.g. `svn://host/repo`, `git://host/repo`, or a plain `https://github.com/...` URL routed through the VCS path. Triggered during `get_url_rev_and_auth(url)` at the `if '+' not in scheme:` check.
Common situations: Copying a repo browse URL from a browser instead of the VCS install form; misconfiguring `requirements.txt` or `dependency_links`; forgetting the `git+`/`svn+`/`hg+`/`bzr+` prefix; using `svn://` or `git://` protocols directly which lack a `+<protocol>` separator.
Related errors
- The URL {url!r} has an empty revision (after @) which is not
- {editable_req} is not a valid editable requirement. It shoul
- Could not detect requirement name for '{editable_req}', plea
- unexpected show-ref line: {line!r}
- Cannot find command {cls.name!r} - invalid PATH
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/1b6b9593776bd08c.json.
Report an issue: GitHub.