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
- Edit the package metadata source (pyproject.toml [project.urls], setup.cfg, or setup.py) to remove duplicate labels.
- Use distinct labels for each URL (e.g. 'Homepage' and 'Source' instead of two 'Homepage' entries).
- Regenerate the wheel/sdist metadata after fixing the source.
- 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
- Use unique labels for every entry in [project.urls] in pyproject.toml.
- Validate generated metadata before publishing: `twine check dist/*`.
- When parsing third-party metadata, catch KeyError around Project-URL parsing and degrade gracefully.
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
- unable to parse identification
- invalid magic: {magic!r}
- unrecognized capacity ({self.capacity}) or encoding ({self.e
- unable to parse machine and section information
- payload in an invalid encoding
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/6ae586fc130f40b3.
Report an issue: GitHub.