name: windows-filename-guidance os: win32 description: "Use for Windows filename rules and failures involving reserved DOS device names such as NUL, invalid or trailing characters, inaccessible cross-platform names, long paths, or files that Windows cannot inspect, rename, or delete normally."
Windows Filename Guidance
Model
Separate three layers:
- the filesystem's on-disk rules
- Windows path namespace and normalization
- the application's API and path handling
A name can exist on NTFS yet be unreachable through an ordinary Win32 path. This commonly happens when another operating system, namespace, share, or tool created it. It does not by itself prove corruption or bad permissions.
Normal Windows Names
For portable Windows names:
- Do not use control characters U+0000 through U+001F or
<>:"/\|?*. - Do not end a component with a space or period; Win32 normally strips them.
- Do not use
.or..as components. - Do not use
CON,PRN,AUX,NUL,COM1throughCOM9, orLPT1throughLPT9, regardless of case. An extension does not help:NUL.txtstill resolves asNUL. - Treat
:as the drive/alternate-data-stream separator, not filename text. - Assume case-insensitive lookup even though case is preserved.
- Keep each component within the filesystem limit. Path-length support depends on the filesystem, API, application, and whether long paths are enabled.
Use these rules when generating archives, repositories, build output, or shared files intended for Windows.
Diagnose
-
Get the exact absolute path, error text, filesystem, and whether the target is a file or directory.
-
Enumerate the parent rather than probing the suspect name directly:
cmd.exe /d /c dir /a /x "C:\absolute\parent" -
Classify the failure:
- reserved device name
- trailing space/period or other Win32-invalid name
- path too long for the current tool
- open handle
- ACL/ownership denial
- filesystem corruption supported by disk or event-log errors
-
Prefer the same namespace, share, or program that created the entry when it can safely rename or remove it.
Never use Test-Path NUL or if exist NUL to prove a file exists; those can
address the device.
Extended Paths
For an existing local entry that ordinary Win32 normalization cannot address, use an exact absolute extended path:
\\?\C:\absolute\path
For UNC paths, convert \\server\share\path to:
\\?\UNC\server\share\path
The \\?\ prefix bypasses ordinary Win32 parsing; it does not bypass the
filesystem's rules, ACLs, locks, or corruption. Do not combine it with a
relative path.
Git Bash Boundary
Git Bash/MSYS2 rewrites path-looking arguments passed to native Windows
executables. This can also affect shell program text passed as one argument,
such as a remote command containing 2>/dev/null. Preserve literal POSIX text
at that boundary with the documented exclusion variable:
MSYS2_ARG_CONV_EXCL='*' podman.exe machine ssh 'command 2>/dev/null'
Do not assume MSYS_NO_PATHCONV covers every native executable.
Bash double quotes reduce a leading \\ to \. Therefore
"\\?\C:\absolute\path\NUL" reaches a child process as the malformed
\?\C:\absolute\path\NUL. When invoking cmd.exe from Bash, preserve the
literal path and disable MSYS2 argument conversion:
MSYS2_ARG_CONV_EXCL='*' cmd.exe /d /c del '\\?\C:\absolute\path\NUL'
The cmd examples below are native cmd.exe syntax. Do not paste their quoted
paths unchanged into a Bash command.
Delete Problem Entries
Show the exact target and obtain explicit confirmation first. Reject wildcards, relative paths, drive/share roots, and ambiguous targets.
If Git Bash created the entry and can enumerate its exact absolute POSIX path, prefer removing it through that same layer:
rm -- "$PWD/NUL"
File through native cmd.exe:
cmd.exe /d /c del "\\?\C:\absolute\path\NUL"
Read-only file, only after reporting why:
cmd.exe /d /c del /f "\\?\C:\absolute\path\NUL"
Empty directory:
cmd.exe /d /c rd "\\?\C:\absolute\path\NUL"
Nonempty directory: enumerate its contents and obtain separate recursive-delete confirmation before running:
cmd.exe /d /c rd /s /q "\\?\C:\absolute\path\NUL"
Preserve exact spelling, extension, spaces, and periods. Enumerate the parent again; report success only when the literal entry is absent.
git status is not direct filesystem verification. If deletion succeeds and
parent enumeration no longer contains the literal entry, but Git still reports
it, bypass its filesystem monitor and untracked cache once:
git -c core.fsmonitor=false -c core.untrackedCache=false status --short
If that removes the report and the repository uses the built-in monitor, restart it. Do not issue further deletion commands solely because cached Git status still names the entry.
If the extended path still fails, use dir /a /x on the parent. Verify the 8.3
short name maps to the exact target before proposing deletion through that name.
Other Causes
- Open handle: identify and close the owning process before retrying.
- ACL denial: inspect ownership and permissions; request elevation or ownership changes only when confirmed necessary.
- Deep path: use an extended path, shorten a parent, map a deeper drive/share, or use a tool that supports the full path.
- Corruption: run filesystem repair only when independent evidence supports it; an odd filename alone is insufficient.
- Missing parent entry: stop. A successful reference to
NULalone is the null device, not evidence of a file.