pypa/pip · error · InstallationError

The URL {url!r} has an empty revision (after @) which is not

Error message

The URL {url!r} has an empty revision (after @) which is not supported. Include a revision after @ or remove @ from the URL.

What it means

Raised by `get_url_rev_and_auth` when a VCS URL contains an `@` separator in the path (the revision delimiter) but the segment after the final `@` is empty. pip interprets `<url>@<rev>` to pin a specific revision; an empty rev (`...@` or `...@#egg=...`) is ambiguous and unsupported, so it aborts with an InstallationError rather than silently installing HEAD.

Source

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

        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, ""))
        return url, rev, user_pass

    @staticmethod
    def make_rev_args(username: str | None, password: HiddenText | None) -> CommandArgs:
        """
        Return the RevOptions "extra arguments" to use in obtain().
        """
        return []

    def get_url_rev_options(self, url: HiddenText) -> tuple[HiddenText, RevOptions]:
        """
        Return the URL and RevOptions object to use in obtain(),

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Append a concrete revision after `@`, e.g. `git+https://host/repo.git@v1.2.3`.
  2. If you want the default/HEAD revision, remove the trailing `@` entirely.
  3. Check that any variable substituted after `@` (tag, commit SHA, branch) is non-empty in your CI/requirements template.

Example fix

# before
pip install git+https://github.com/org/repo.git@

# after
pip install git+https://github.com/org/repo.git@v1.2.3
# or, for HEAD:
pip install git+https://github.com/org/repo.git
Defensive patterns

Strategy: validation

Validate before calling

import urllib.parse

def validate_vcs_rev(url: str) -> None:
    path = urllib.parse.urlsplit(url).path
    if "@" in path:
        rev = path.rsplit("@", 1)[1]
        if not rev:
            raise ValueError(f"Empty revision after @ in {url!r}")

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Pip parses a VCS URL where `'@' in path` is True, then `path.rsplit('@', 1)` yields an empty rev string. Happens for URLs like `git+https://host/repo.git@` or `svn+https://host/repo/@#egg=Pkg` where nothing follows the `@` before the fragment/query.

Common situations: Typos in `requirements.txt`; copy-pasting a URL and truncating the revision; templating scripts that substitute an empty commit/tag variable after `@`; CI configs where a branch/tag env var is unset.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/1a226a5f44785e5c.json. Report an issue: GitHub.