nodejs/node · warning · KeyError

duplicate labels in project urls

Error message

duplicate labels in project urls

What it means

Raised by the Project-URL metadata parser (_parse_project_urls) when the same label appears more than once in a Project-URL field. The Project-URL field uses 'Label, URL' comma-separated entries; labels must be unique. When a duplicate label is detected, the parser raises KeyError, which the higher-level metadata parsing logic catches to mark the entire field as unparsable rather than silently overwriting a value.

Source

Thrown at tools/gyp/pylib/packaging/metadata.py:210

        # The other potential issue is that it's possible to have the
        # same label multiple times in the metadata, with no solid "right"
        # answer with what to do in that case. As such, we'll do the only
        # thing we can, which is treat the field as unparsable and add it
        # to our list of unparsed fields.
        parts = [p.strip() for p in pair.split(",", 1)]
        parts.extend([""] * (max(0, 2 - len(parts))))  # Ensure 2 items

        # TODO: The spec doesn't say anything about if the keys should be
        #       considered case sensitive or not... logically they should
        #       be case-preserving and case-insensitive, but doing that
        #       would open up more cases where we might have duplicate
        #       entries.
        label, url = parts
        if label in urls:
            # The label already exists in our set of urls, so this field
            # is unparsable, and we can just add the whole thing to our
            # unparsable data and stop processing it.
            raise KeyError("duplicate labels in project urls")
        urls[label] = url

    return urls


def _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str:
    """Get the body of the message."""
    # If our source is a str, then our caller has managed encodings for us,
    # and we don't need to deal with it.
    if isinstance(source, str):
        payload: str = msg.get_payload()
        return payload
    # If our source is a bytes, then we're managing the encoding and we need
    # to deal with it.
    else:
        bpayload: bytes = msg.get_payload(decode=True)
        try:
            return bpayload.decode("utf8", "strict")

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Edit the package metadata source (pyproject.toml [project.urls], setup.cfg, or setup.py) to remove duplicate labels.
  2. Use distinct labels for each URL (e.g. 'Homepage' and 'Source' instead of two 'Homepage' entries).
  3. Regenerate the wheel/sdist metadata after fixing the source.
  4. If parsing third-party metadata you do not control, catch KeyError and treat the Project-URL field as missing/unparsed.

Example fix

# before (pyproject.toml)
[project.urls]
Homepage = 'https://example.com'
Homepage = 'https://example.org'  # duplicate label
# after
[project.urls]
Homepage = 'https://example.com'
Source = 'https://example.org'
Defensive patterns

Strategy: try-catch

Validate before calling

# Deduplicate Project-URL labels before building metadata
urls = {'Homepage': 'https://a.com', 'Source': 'https://b.com'}
if len(urls) != len(set(urls)):
    raise ValueError('duplicate labels in project urls')

Type guard

def has_unique_url_labels(url_strings: list[str]) -> bool:
    labels = [s.split(',', 1)[0].strip() for s in url_strings]
    return len(labels) == len(set(labels))

Try / catch

try:
    urls = _parse_project_urls(raw_field)
except KeyError as e:
    if 'duplicate labels' in str(e):
        # field is unparsable; fall back to empty urls
        urls = {}

Prevention

When it happens

Trigger: Parsing package metadata (METADATA or PKG-INFO) whose Project-URL header contains two entries with the same label, e.g. two lines both starting with 'Homepage,'. The parser accumulates labels into a dict and raises on the second occurrence.

Common situations: A package's metadata was hand-authored with duplicate labels. A build tool (setuptools/hatch/flit) merged multiple metadata sources and created duplicate Project-URL labels. An sdist or wheel with slightly malformed metadata.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/6ae586fc130f40b3. Report an issue: GitHub.