{"record":{"id":"b40fc605d8643394","repo":"nodejs/node","slug":"invalid-sdist-filename-extension-must-be-tar-gz","errorCode":null,"errorMessage":"Invalid sdist filename (extension must be '.tar.gz' or '.zip'): {filename}","messagePattern":"Invalid sdist filename \\(extension must be '\\.tar\\.gz' or '\\.zip'\\): (.+?)","errorType":"validation","errorClass":"InvalidSdistFilename","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/packaging/utils.py","lineNumber":152,"sourceCode":"        build_match = _build_tag_regex.match(build_part)\n        if build_match is None:\n            raise InvalidWheelFilename(\n                f\"Invalid build number: {build_part} in '{filename}'\"\n            )\n        build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))\n    else:\n        build = ()\n    tags = parse_tag(parts[-1])\n    return (name, version, build, tags)\n\n\ndef parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]:\n    if filename.endswith(\".tar.gz\"):\n        file_stem = filename[: -len(\".tar.gz\")]\n    elif filename.endswith(\".zip\"):\n        file_stem = filename[: -len(\".zip\")]\n    else:\n        raise InvalidSdistFilename(\n            f\"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):\"\n            f\" {filename}\"\n        )\n\n    # We are requiring a PEP 440 version, which cannot contain dashes,\n    # so we split on the last dash.\n    name_part, sep, version_part = file_stem.rpartition(\"-\")\n    if not sep:\n        raise InvalidSdistFilename(f\"Invalid sdist filename: {filename}\")\n\n    name = canonicalize_name(name_part)\n\n    try:\n        version = Version(version_part)\n    except InvalidVersion as e:\n        raise InvalidSdistFilename(\n            f\"Invalid sdist filename (invalid version): {filename}\"\n        ) from e","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/packaging/utils.py#L134-L170","documentation":"InvalidSdistFilename raised by parse_sdist_filename when the filename ends with neither '.tar.gz' nor '.zip', the only two sdist extensions packaging recognizes.","triggerScenarios":"Calling parse_sdist_filename on a wheel ('.whl'), a '.tar.bz2', a raw directory, or any non-sdist string.","commonSituations":"Routing all distribution filenames through one parser, or encountering newer/experimental sdist formats not covered by this vendored packaging version.","solutions":["Branch on extension: .whl -> parse_wheel_filename; .tar.gz/.zip -> parse_sdist_filename.","Filter inputs to only sdist files before calling parse_sdist_filename.","Catch InvalidSdistFilename and skip unknown files when scanning a directory."],"exampleFix":"# before\nname, ver = parse_sdist_filename(fname)\n\n# after\nif fname.endswith(('.tar.gz', '.zip')):\n    name, ver = parse_sdist_filename(fname)\nelse:\n    raise ValueError(f'Not an sdist: {fname}')","handlingStrategy":"type-guard","validationCode":"def is_sdist(filename: str) -> bool:\n    return filename.endswith('.tar.gz') or filename.endswith('.zip')","typeGuard":"from pathlib import Path\ndef is_sdist_path(p) -> bool:\n    name = Path(p).name\n    return name.endswith('.tar.gz') or name.endswith('.zip')","tryCatchPattern":"try:\n    name, ver = parse_sdist_filename(fn)\nexcept InvalidSdistFilename:\n    pass  # route to wheel parser or skip","preventionTips":["Branch on extension before parsing.","Filter mixed file lists by suffix.","Recognize only .tar.gz and .zip as sdists."],"tags":["packaging","utils","sdist","filename"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}