Windows: Real nul files from older versions require \\?\ path for detection and cleanup

Resolved 💬 2 comments Opened Mar 5, 2026 by nathanbybee Closed Apr 2, 2026

Windows: Real nul files from older versions require \\\\?\\ path for detection and cleanup

Environment

  • Claude Code version at time of contamination: 2.1.39 (or earlier)
  • Claude Code version tested: 2.1.69 (fix confirmed working — no new nul files created)
  • OS: Windows 11 Home 10.0.26200
  • Filesystem: C: (NTFS), D: (ReFS Dev Drive), H: (NTFS)
  • Shell: Git Bash (Claude Code's Bash tool)

Related Issues

  • #4928 - [BUG] file named nul created on windows (closed, locked)
  • #17783 - Windows: Temp directories and nul files not cleaned up (closed, locked)
  • #17925 - Temporary files not cleaned up on Windows (closed, locked)

TL;DR

Claude Code versions before ~2.1.42 created real nul files on Windows via Git Bash's > nul redirection. The fix is confirmed working in 2.1.69. However:

  1. Orphaned real nul files persist from older versions and cause sync/backup tool failures
  2. The obvious cleanup method doesn't workRemove-Item and del silently no-op against files named nul because Windows resolves the name to the NUL device
  3. The obvious detection method is wildly misleadingGet-ChildItem -Filter 'nul' returns phantom device node entries in every directory on the system, inflating counts by orders of magnitude
  4. Correct cleanup requires the \\?\ path prefix to bypass Windows device name resolution

The detection pitfall

On Windows, NUL is a reserved device name (like COM1, LPT1). When PowerShell's Get-ChildItem -Filter 'nul' enumerates a directory, it reports a phantom entry for the NUL device — even in brand-new empty directories:

# Phantom nul appears in a directory that was just created empty
$dir = "C:\test_phantom"
New-Item -ItemType Directory -Path $dir -Force | Out-Null
(Get-ChildItem -Path $dir -Filter 'nul' -File).FullName  # Returns "C:\test_phantom\nul"
Remove-Item $dir -Force

A naive recursive scan reports one nul "file" per directory on the system. On my machine: 133,646 phantom entries across three drives (C:, D:, H:). These are not real files — they can't be read, can't be deleted, and appear identically in directories Claude Code has never touched.

How to find REAL nul files

The \\?\ path prefix tells Windows to treat nul as a literal filename, bypassing device name resolution. Only real files show up:

# Correct detection: only finds real nul files, not phantom device nodes
Get-ChildItem -Path "D:\" -Recurse -Filter 'nul' -File -ErrorAction SilentlyContinue |
    Where-Object {
        try {
            $found = [System.IO.Directory]::GetFiles("\\?\$($_.DirectoryName)", "nul")
            $found.Count -gt 0
        } catch { $false }
    } |
    ForEach-Object { Write-Host $_.FullName }

On my system this found 4 real nul files (vs 133,646 phantom entries):

C:\Users\[USER]\.claude\nul
D:\dev\nul
D:\dev\projects\[PROJECT]\nul
H:\Data\raw-data\[PROJECT]\nul

Every one was in a directory Claude Code had used as a working directory.

How to delete real nul files

Standard deletion methods silently fail because Windows resolves nul to the NUL device:

# These all silently "succeed" but don't actually delete anything:
Remove-Item "path\nul" -Force                          # No-op
Remove-Item -LiteralPath "\\?\path\nul" -Force         # No-op (Remove-Item still resolves)
cmd /c 'del /f "path\nul"'                             # "syntax is incorrect"
[System.IO.File]::Delete("path\nul")                   # Tries \\.\nul, access denied

The only method that works:

# Delete via \\?\ prefix using .NET — bypasses device name resolution
[System.IO.File]::Delete("\\?\C:\Users\[USER]\.claude\nul")

Complete cleanup script

# Save as cleanup_real_nul.ps1 and run with:
# powershell -ExecutionPolicy Bypass -File cleanup_real_nul.ps1

$drives = @("C:\", "D:\", "H:\")  # Adjust to your drives
$deleted = 0
$phantoms = 0

foreach ($drive in $drives) {
    Write-Host "Scanning $drive ..." -ForegroundColor Cyan
    Get-ChildItem -Path $drive -Recurse -Filter 'nul' -File -ErrorAction SilentlyContinue |
        ForEach-Object {
            $longDir = "\\?\$($_.DirectoryName)"
            try {
                $found = [System.IO.Directory]::GetFiles($longDir, "nul")
                if ($found.Count -gt 0) {
                    $longPath = "\\?\$($_.FullName)"
                    [System.IO.File]::Delete($longPath)
                    # Verify
                    $still = [System.IO.Directory]::GetFiles($longDir, "nul")
                    if ($still.Count -eq 0) {
                        Write-Host "  DELETED: $($_.FullName)" -ForegroundColor Green
                        $deleted++
                    } else {
                        Write-Host "  FAILED:  $($_.FullName)" -ForegroundColor Red
                    }
                } else {
                    $phantoms++
                }
            } catch {
                $phantoms++
            }
        }
}

Write-Host ""
Write-Host "Real nul files deleted: $deleted" -ForegroundColor Green
Write-Host "Phantom device nodes skipped: $phantoms" -ForegroundColor Gray

Why this matters

Real nul files cause problems for file sync and backup tools:

  • Tresorit: Flags nul as an invalid filename that can't sync
  • FreeFileSync: Requires explicit exclusion filters for nul (never needed before Claude Code)
  • iDrive: High CPU usage and errors when encountering nul files (also reported by @trueliarx on #17783)

These tools use Win32 FindFirstFile which correctly distinguishes real files from device nodes — so they only flag the real Claude-created files, not the phantoms. But users investigating the problem with PowerShell will see 100K+ phantom entries and assume the problem is far worse than it is.

Fix verification

Tested on version 2.1.69: deleted all 4 real nul files, ran a full session with multiple Bash tool invocations (git status, python --version, ls), re-scanned — zero new real nul files created. The fix in ~2.1.42 is working.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗