{"record":{"id":"6ae586fc130f40b3","repo":"nodejs/node","slug":"duplicate-labels-in-project-urls","errorCode":null,"errorMessage":"duplicate labels in project urls","messagePattern":"duplicate labels in project urls","errorType":"exception","errorClass":"KeyError","httpStatus":null,"severity":"warning","filePath":"tools/gyp/pylib/packaging/metadata.py","lineNumber":210,"sourceCode":"        # The other potential issue is that it's possible to have the\n        # same label multiple times in the metadata, with no solid \"right\"\n        # answer with what to do in that case. As such, we'll do the only\n        # thing we can, which is treat the field as unparsable and add it\n        # to our list of unparsed fields.\n        parts = [p.strip() for p in pair.split(\",\", 1)]\n        parts.extend([\"\"] * (max(0, 2 - len(parts))))  # Ensure 2 items\n\n        # TODO: The spec doesn't say anything about if the keys should be\n        #       considered case sensitive or not... logically they should\n        #       be case-preserving and case-insensitive, but doing that\n        #       would open up more cases where we might have duplicate\n        #       entries.\n        label, url = parts\n        if label in urls:\n            # The label already exists in our set of urls, so this field\n            # is unparsable, and we can just add the whole thing to our\n            # unparsable data and stop processing it.\n            raise KeyError(\"duplicate labels in project urls\")\n        urls[label] = url\n\n    return urls\n\n\ndef _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str:\n    \"\"\"Get the body of the message.\"\"\"\n    # If our source is a str, then our caller has managed encodings for us,\n    # and we don't need to deal with it.\n    if isinstance(source, str):\n        payload: str = msg.get_payload()\n        return payload\n    # If our source is a bytes, then we're managing the encoding and we need\n    # to deal with it.\n    else:\n        bpayload: bytes = msg.get_payload(decode=True)\n        try:\n            return bpayload.decode(\"utf8\", \"strict\")","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/packaging/metadata.py#L192-L228","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before (pyproject.toml)\n[project.urls]\nHomepage = 'https://example.com'\nHomepage = 'https://example.org'  # duplicate label\n# after\n[project.urls]\nHomepage = 'https://example.com'\nSource = 'https://example.org'","handlingStrategy":"try-catch","validationCode":"# Deduplicate Project-URL labels before building metadata\nurls = {'Homepage': 'https://a.com', 'Source': 'https://b.com'}\nif len(urls) != len(set(urls)):\n    raise ValueError('duplicate labels in project urls')","typeGuard":"def has_unique_url_labels(url_strings: list[str]) -> bool:\n    labels = [s.split(',', 1)[0].strip() for s in url_strings]\n    return len(labels) == len(set(labels))","tryCatchPattern":"try:\n    urls = _parse_project_urls(raw_field)\nexcept KeyError as e:\n    if 'duplicate labels' in str(e):\n        # field is unparsable; fall back to empty urls\n        urls = {}","preventionTips":["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."],"tags":["packaging","metadata","pep621","parsing"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}