{"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":"validation","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/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L458-L494","documentation":"Raised by convert_path() on non-Unix-like systems when the pathname ends with '/'. A trailing separator would collapse to an empty final segment after splitting on '/', which is ambiguous for native path joining, so a ValueError is raised. Like the absolute-path check, it only triggers when os.sep != '/'.","triggerScenarios":"Calling convert_path('src/mypkg/') on Windows, or setup() receiving a directory-style path with a trailing slash on Windows.","commonSituations":"Path joining code that appends '/' unconditionally, or copy-pasted directory paths that retain their trailing separator.","solutions":["Strip the trailing '/': convert_path('src/mypkg'.rstrip('/')).","Build paths with os.path.join and strip the separator before passing to convert_path.","Validate with 'if pathname.endswith(\"/\"): pathname = pathname[:-1]'."],"exampleFix":"// before\nconvert_path('src/mypkg/')  # on Windows\n// after\nconvert_path('src/mypkg')","handlingStrategy":"validation","validationCode":"import os\ndef safe_convert_path(p):\n    if os.sep != '/' and p.endswith('/'):\n        p = p.rstrip('/')\n    from distlib.util import convert_path\n    return convert_path(p)","typeGuard":null,"tryCatchPattern":"from distlib.util import convert_path\ntry:\n    native = convert_path(p)\nexcept ValueError as e:\n    if 'cannot end with' in str(e):\n        native = convert_path(p.rstrip('/'))\n    else:\n        raise","preventionTips":["Normalize paths with p.rstrip('/') before convert_path().","Build paths with os.path.join rather than string concatenation with '/'.","Run packaging tests on Windows to catch platform-specific path issues."],"tags":["path","filesystem","windows","cross-platform","packaging"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}