{"record":{"id":"ebe56e225dba2514","repo":"pypa/pip","slug":"path-s-cannot-end-with","errorCode":null,"errorMessage":"path '%s' cannot end with '/'","messagePattern":"path '(.+?)' cannot end with '/'","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":476,"sourceCode":"\ndef convert_path(pathname):\n    \"\"\"Return 'pathname' as a name that will work on the native filesystem.\n\n    The path is split on '/' and put back together again using the current\n    directory separator.  Needed because filenames in the setup script are\n    always supplied in Unix style, and have to be converted to the local\n    convention before we can actually use them in the filesystem.  Raises\n    ValueError on non-Unix-ish systems if 'pathname' either starts or\n    ends with a slash.\n    \"\"\"\n    if os.sep == '/':\n        return pathname\n    if not pathname:\n        return pathname\n    if pathname[0] == '/':\n        raise ValueError(\"path '%s' cannot be absolute\" % pathname)\n    if pathname[-1] == '/':\n        raise ValueError(\"path '%s' cannot end with '/'\" % pathname)\n\n    paths = pathname.split('/')\n    while os.curdir in paths:\n        paths.remove(os.curdir)\n    if not paths:\n        return os.curdir\n    return os.path.join(*paths)\n\n\nclass FileOperator(object):\n\n    def __init__(self, dry_run=False):\n        self.dry_run = dry_run\n        self.ensured = set()\n        self._init_record()\n\n    def _init_record(self):\n        self.record = False","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/distlib/util.py#L458-L494","documentation":"Raised as ValueError by distlib.util.convert_path on non-Unix systems when the pathname ends with '/'. A trailing slash denotes a directory, which convert_path rejects because it is meant for file-name translation in setup scripts, not directory specifiers. Like the absolute-path check, it only fires when os.sep != '/' (Windows).","triggerScenarios":"Calling convert_path('pkg/data/') on Windows; setup.py package_data or scripts entries that accidentally include a trailing slash; normalizing user-supplied path strings that end with '/' on Windows.","commonSituations":"Trailing slashes left over from os.path.join with an empty final component; copy-pasted directory paths from Unix examples used in packaging config on Windows.","solutions":["Strip trailing '/' before calling: pathname = pathname.rstrip('/').","Validate with: assert not pathname.endswith('/') on Windows before convert_path.","Construct paths from explicit components rather than string concatenation that can leave a dangling slash."],"exampleFix":"# before\nconvert_path('pkg/resources/')\n# after\nconvert_path('pkg/resources')","handlingStrategy":"validation","validationCode":"def safe_convert_path(pathname):\n    if pathname.endswith('/'):\n        pathname = pathname.rstrip('/')\n    return convert_path(pathname)","typeGuard":"def has_no_trailing_slash(s: str) -> bool:\n    return isinstance(s, str) and (not s or not s.endswith('/'))","tryCatchPattern":"try:\n    convert_path(pathname)\nexcept ValueError as e:\n    if 'cannot end' in str(e):\n        convert_path(pathname.rstrip('/'))","preventionTips":["rstrip('/') on user path input.","Avoid joining with an empty trailing component."],"tags":["filesystem","paths","windows","distlib","packaging"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}