Skip to main content

Windows reserved device names break git (AUX, CON, PRN, NUL, COM, LPT)

TL;DR

Windows reserves a handful of file and folder names for DOS-era hardware devices. They cannot be used as a file or folder name anywhere in a path:

  • CON, PRN, AUX, NUL
  • COM0COM9, COM¹, COM², COM³
  • LPT0LPT9, LPT¹, LPT², LPT³

The rule is case-insensitive (aux = AUX = Aux) and applies even with an extension (aux.txt, nul.tar.gz are also treated as the device).

It applies to any segment of the path — public/aux/foo.svg fails for the same reason as aux/foo.svg.


Problem

In a Cangjie-practice project, 495 auxiliary glyph SVGs were downloaded into public/aux/, ready to git add and push.

Symptoms

$ git add public/aux/Cjem-a0-1.svg
error: open("public/aux/Cjem-a0-1.svg"): No such file or directory
error: unable to index file 'public/aux/Cjem-a0-1.svg'
fatal: adding files failed

Yet the same file:

  • ls public/aux/Cjem-a0-1.svg lists it with the correct size
  • Get-Content public/aux/Cjem-a0-1.svg reads its contents
  • stat reports all metadata as normal
  • git ls-files --others --exclude-standard public/aux/ enumerates it

Copying the same file to public/_test_dir/test.svg makes git add succeed immediately. So the problem is not the file — it is the directory name aux.

Environment

  • Windows 11
  • Git for Windows (mingw-compiled git.exe)
  • PowerShell 7+ and cmd.exe
  • NTFS volume

The clue that gave it away

Trying Rename-Item public/aux public/aux_temp produced:

Cannot rename the specified target, because it represents a path or device name.

The phrase "represents a path or device name" is the giveaway — Windows is treating aux as a device, not a folder.

Solution

  1. Rename via cmd.exe (PowerShell's Rename-Item refuses, but cmd's move works):

    cmd /c "move public\aux public\auxiliary"
  2. Update every reference in code: <img src="aux/..."><img src="auxiliary/...">, OUT_DIR = 'public/aux''public/auxiliary'.

  3. git add succeeds immediately afterwards.

Root cause

A compatibility burden carried forward from MS-DOS. The OS reserved names for hardware devices (auxiliary device, console, printer, the null sink) so programs could "read and write devices as if they were files."

DOS-era example — sending text to the screen:

COPY my_text.txt CON

This "copies" the contents of my_text.txt to the CON (console) device.

To keep three-decade-old programs running, Windows preserves the rule. Even on Windows 11, Get-Content > CON in PowerShell still writes to the console.

Why ls / stat / Get-Content see the folder but git's open() does not

Two different syscall paths:

  • Directory enumeration (FindFirstFile / readdir) lists actual NTFS entries, including a directory literally named aux. So ls, stat, and git ls-files all see it.
  • Opening by path (CreateFile / POSIX open) makes Windows perform "DOS device name resolution" before touching disk. When it sees aux, it opens the device — never the directory of the same name on disk. So git's open("public/aux/...") receives ENOENT.

PowerShell's Get-Content goes through .NET, which in some versions automatically prefixes \\?\ to bypass the resolver — so it reads the file fine. But git.exe is a mingw-compiled native program calling Win32 APIs directly, without that workaround.


Trivia: \\?\ prefix bypasses the rule

Windows APIs accept a \\?\ path prefix that skips all DOS path parsing, including the device-name check.

# Normal mkdir is blocked
mkdir aux
# Cannot create the specified target...

# With the \\?\ prefix it succeeds
[System.IO.Directory]::CreateDirectory('\\?\D:\test\aux')

Do not actually do this. The resulting aux folder cannot be opened in File Explorer or removed with rm / Remove-Item — only further \\?\ calls can manipulate it. It is very easy to create something you cannot clean up.

If the word aux is semantically necessary, use a variant: aux_, auxi, auxiliary, aux-shapes.


Checklist: filter before creating a new file or folder

In any cross-platform project, before committing to a directory or file name, verify:

  • Not one of CON / PRN / AUX / NUL
  • Not one of COM0COM9
  • Not one of LPT0LPT9
  • Not used as the base name with an extension (aux.txt, nul.json are also blocked)
  • If sharing code with teammates: a name that works fine on Linux/macOS may explode on Windows

Anyone who hits this typically assumes their Git install is broken, the file is corrupt, or it is a permissions issue — and only finds the real cause after a long detour.

References