You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
os.stat() on Windows succeeds for nonexistent paths with trailing spaces #84419
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2020-04-09.14:25:30.374>labels= ['3.8', 'type-bug', 'library', '3.9', 'OS-windows']
title='os.stat() on Windows succeeds for nonexistent paths with trailing spaces'updated_at=<Date2020-04-10.13:57:20.426>user='https://github.com/laloch'
os.stat() seems to be striping significant trailing spaces off
the path argument
The Windows file API normalizes paths to replace forward slashes with backslashes; resolve relative paths and "." and ".." components; strip trailing spaces and dots from the final component; and map reserved DOS device names in the final component of non-UNC paths to device paths (e.g. "C:/Temp/con " -> r"\\.\con").
Use a "\\?\" extended device path to bypass normalization, e.g. r"\\?\C:\Program Files ". Because an extended path doesn't get normalized in an open or create context, it should only use backslash as the path separator and should be fully qualified, without any "." and ".." components. Also, a non-device UNC path has to explicitly use the "UNC" device, e.g. "//server/share/spam... " -> r"\\?\UNC\server\share\spam... ".
That said, I strongly advise against using an extended path to create or rename files to use reserved DOS device names or trailing dots and spaces. Such filenames will be inaccessible by most applications.
Ignoring trailing spaces in the hack to check extensions in stat is the only viable way (and strictly speaking they should only be ignored when the path doesn't start with a \\?\ prefix). (I considered getting the final path from the open file handle, but I'm pretty sure CreateProcess validates the path given to it rather than the resolved file, and so e.g. a symlink without .exe isn't executable even if the target is one.)
We should avoid modifying paths that come from the OS, to ensure that users can round-trip paths perfectly. normpath is our exception, but it's also not required, and so our own functions that try to interpret paths need to handle some of the normalisation that the OS would do when actually resolving the path.
The difference between os.stat() and os.scandir()/os.listdir() was fixed in gh-150880: the path is now normalized before appending the wildcard, so all of them ignore trailing dots and spaces.
What is left is the execute permissions in st_mode, which were guessed from the not normalized extension. #155392 fixes this.
Activity
laloch commentedon Apr 9, 2020
On Windows (Server 2012 R2 in my case) os.stat() seems to be striping significant trailing spaces off the path argument:
The same also applies to regular files, not just directories.
eryksun commentedon Apr 9, 2020
The Windows file API normalizes paths to replace forward slashes with backslashes; resolve relative paths and "." and ".." components; strip trailing spaces and dots from the final component; and map reserved DOS device names in the final component of non-UNC paths to device paths (e.g. "C:/Temp/con " -> r"\\.\con").
Use a "\\?\" extended device path to bypass normalization, e.g. r"\\?\C:\Program Files ". Because an extended path doesn't get normalized in an open or create context, it should only use backslash as the path separator and should be fully qualified, without any "." and ".." components. Also, a non-device UNC path has to explicitly use the "UNC" device, e.g. "//server/share/spam... " -> r"\\?\UNC\server\share\spam... ".
That said, I strongly advise against using an extended path to create or rename files to use reserved DOS device names or trailing dots and spaces. Such filenames will be inaccessible by most applications.
18 remaining items
zooba commentedon May 19, 2025
Ignoring trailing spaces in the hack to check extensions in stat is the only viable way (and strictly speaking they should only be ignored when the path doesn't start with a
\\?\prefix). (I considered getting the final path from the open file handle, but I'm pretty sureCreateProcessvalidates the path given to it rather than the resolved file, and so e.g. a symlink without.exeisn't executable even if the target is one.)We should avoid modifying paths that come from the OS, to ensure that users can round-trip paths perfectly.
normpathis our exception, but it's also not required, and so our own functions that try to interpret paths need to handle some of the normalisation that the OS would do when actually resolving the path.serhiy-storchaka commentedon Aug 8, 2026
The difference between
os.stat()andos.scandir()/os.listdir()was fixed in gh-150880: the path is now normalized before appending the wildcard, so all of them ignore trailing dots and spaces.What is left is the execute permissions in
st_mode, which were guessed from the not normalized extension. #155392 fixes this.Merge remote-tracking branch 'upstream/main' into pythongh-84419-stat…
gh-84419: Fix the execute permissions in os.stat() on Windows (GH-155392
[3.13] gh-84419: Fix the execute permissions in os.stat() on Windows (G…
[3.14] gh-84419: Fix the execute permissions in os.stat() on Windows (G…
pythongh-84419: Fix the execute permissions in os.stat() on Windows (p…
[3.15] gh-84419: Fix the execute permissions in os.stat() on Windows (G…