After a macOS crash (kernel panic, power loss, forced restart), external ExFAT drives often become unmountable. macOS detects filesystem corruption but cannot repair it, leaving you stuck in a loop where every mount attempt fails.
This guide explains what's happening at the filesystem level and how to fix it — from a quick read-only mount to a full bitmap rebuild that restores read-write access.
- The Problem
- Why macOS Makes This Hard
- Quick Fix: Read-Only Mount
- Full Fix: Rebuild the Allocation Bitmap
- ExFAT On-Disk Structure Primer
- Troubleshooting FAQ
When macOS crashes while an ExFAT volume is mounted, two things typically go wrong:
-
The dirty flag stays set. ExFAT sets
VolumeFlagsbit 1 (VolumeDirty) when a volume is mounted for writing. A clean unmount clears it. A crash leaves it set, telling the OS "this filesystem may be inconsistent." -
The allocation bitmap becomes corrupt. The allocation bitmap tracks which clusters are in use. During normal operation, macOS updates the bitmap in memory and flushes it to disk periodically. A crash can leave the on-disk bitmap out of sync with the actual FAT (File Allocation Table), which is the authoritative record of cluster chains.
The result: fsck_exfat detects the bitmap inconsistency, reports "The bitmap needs to be repaired", but then fails to repair it and exits with code 1.
Since diskutil mount runs fsck_exfat before mounting, and fsck fails, the mount is refused:
$ diskutil repairVolume diskXsY
Started file system repair on diskXsY
Performing fsck_exfat -y -x /dev/rdiskXsY
Checking volume
Checking main boot region
Checking system files
Checking upper case translation table
Checking file system hierarchy
Checking active bitmap
The bitmap needs to be repaired
Rechecking main boot region
Rechecking alternate boot region
File system check exit code is 1
Restoring the original state found as unmounted
Error: -69845: File system verify or repair failed
Underlying error: 1
Your drive is fine. Your data is fine. macOS just can't get past the bitmap check.
There are several layers of macOS that conspire to make this recovery harder than it needs to be.
When macOS detects an ExFAT volume, diskarbitrationd automatically launches fsck_exfat to check it. If you kill the fsck process, diskarbitrationd just starts a new one. On a large drive (e.g., 4 TB), each fsck run takes minutes to scan the full filesystem hierarchy before getting to the bitmap check and failing.
# This just makes things worse — it restarts from scratch
$ sudo kill <fsck_exfat_pid>
# diskarbitrationd immediately spawns a new one
Apple's fsck_exfat can identify that the bitmap is inconsistent with the FAT, but its repair capability is limited. For large-scale bitmap corruption (common after crashes), it simply fails. There is no -f force-repair flag. There is no alternative fsck for ExFAT on macOS — fsck.exfat and exfatfsck (from the Linux exfatprogs package) are not available.
You cannot tell diskutil mount to skip the filesystem check. It will always run fsck_exfat first, and if fsck fails, it refuses to mount:
Volume on diskXsY failed to mount
If you think the volume is supported but damaged, try the "readOnly" option
Even diskutil mount readOnly still runs fsck first.
System Integrity Protection (SIP) restricts which processes can perform raw I/O on disk devices. Python, dd run from non-entitled contexts, and other unsigned tools get Operation not permitted when trying to open /dev/rdiskXsY for writing:
$ sudo python3 fix_bitmap.py /dev/rdiskXsY
OSError: [Errno 1] Operation not permitted
Even with sudo, SIP blocks the operation because the binary isn't Apple-signed with the appropriate entitlements.
The workaround: Run commands from Terminal.app (or iTerm, etc.) with Full Disk Access enabled in System Preferences → Privacy & Security → Full Disk Access. The terminal's TCC entitlement is inherited by child processes, including sudo python3 and sudo dd.
Even with proper entitlements, Python's file.write() to a raw device (/dev/rdiskXsY) may fail with OSError: [Errno 5] Input/output error or [Errno 22] Invalid argument. This is because:
- Writes must be aligned to the device's sector size (512 bytes)
- The write size must be a multiple of the sector size
- Some USB-to-SATA bridges are picky about write patterns
The fix: Write to a temp file, then use dd (an Apple-signed binary) to copy it to the device. dd handles alignment and is entitled for raw device I/O.
If you just need to read your files (copy them off the drive), you can bypass fsck entirely using mount_exfat directly:
# 1. Force-unmount to stop any running fsck
sudo diskutil unmountDisk force diskX
# 2. Create a mount point
sudo mkdir -p /Volumes/MyDrive
# 3. Mount read-only, bypassing fsck
sudo mount_exfat -o rdonly /dev/diskXsY /Volumes/MyDriveThis works because mount_exfat doesn't run fsck — it just loads the exfat.kext kernel extension and mounts the volume. The bitmap only matters for write operations (allocating new clusters), so read-only mode is perfectly safe even with a corrupt bitmap.
Once mounted, copy your files off, then proceed to the full fix if you want read-write access restored.
The allocation bitmap can be deterministically rebuilt from the FAT. The FAT is the authoritative record of which clusters belong to which file chains. If a FAT entry is non-zero, the cluster is allocated; if zero, it's free. We just need to walk the FAT and set the corresponding bitmap bits.
- macOS Terminal with Full Disk Access (System Preferences → Privacy & Security → Full Disk Access)
- Python 3 (ships with macOS or Xcode Command Line Tools)
- The drive must be unmounted (we'll force-unmount it)
The companion script fix-exfat-macos.sh automates the entire process:
# Diagnose only (safe, no writes)
sudo bash fix-exfat-macos.sh --dry-run /dev/diskXsY
# Quick read-only mount
sudo bash fix-exfat-macos.sh --read-only /dev/diskXsY
# Full repair (rebuilds bitmap, clears dirty flag, mounts read-write)
sudo bash fix-exfat-macos.sh /dev/diskXsY- Reads the boot sector — parses sector size, cluster size, FAT location, cluster heap offset, cluster count
- Checks the dirty flag — reads
VolumeFlagsat offset0x6Ain the boot sector - Reads the entire FAT — the FAT maps every cluster to its next cluster in a chain (or marks it as free/end-of-chain)
- Rebuilds the bitmap from the FAT — for each cluster: if the FAT entry is non-zero, set the corresponding bit in the bitmap
- Writes the new bitmap to disk — saves to a temp file, then uses
ddto write it to the bitmap's on-disk location - Clears the dirty flag — updates
VolumeFlagsand recalculates the VBR checksum - Mounts the volume — tries
diskutil mountfirst; falls back tomount_exfatif fsck still fails - Disables Spotlight — prevents macOS from immediately hammering the freshly repaired volume with indexing
If you want to understand each step rather than running the script:
# Step 1: Force unmount
sudo diskutil unmountDisk force diskX
# Step 2: Run the bitmap rebuild (embedded in the script, or use the Python directly)
sudo python3 rebuild_bitmap.py /dev/rdiskXsY
# Step 3: Try mounting
sudo diskutil mount diskXsY
# Step 4: If diskutil mount fails (fsck still unhappy), mount directly
sudo mkdir -p /Volumes/MyDrive
sudo mount_exfat /dev/diskXsY /Volumes/MyDrive
# Step 5: Disable Spotlight indexing
sudo mdutil -i off /Volumes/MyDriveUnderstanding the on-disk layout helps you reason about what the repair script is doing and why.
Offset (sectors) Region
───────────────── ────────────────────────────
0 Main Boot Sector (512 bytes)
1-8 Extended Boot Sectors
9-10 OEM Parameters + Reserved
11 VBR Checksum Sector
12-23 Backup Boot Region (copy of 0-11)
24+ FAT Region
24+FAT_LENGTH Cluster Heap (data area)
The exact offsets depend on the volume — they're stored in the boot sector.
Key fields for recovery:
| Offset | Size | Field | Notes |
|---|---|---|---|
0x03 |
8 | FileSystemName | Must be EXFAT |
0x40 |
8 | PartitionOffset | Sectors from disk start |
0x48 |
8 | VolumeLength | Total sectors |
0x50 |
4 | FatOffset | FAT start (sectors from volume start) |
0x54 |
4 | FatLength | FAT size in sectors |
0x58 |
4 | ClusterHeapOffset | Data area start (sectors) |
0x5C |
4 | ClusterCount | Total data clusters |
0x60 |
4 | FirstClusterOfRootDirectory | Root dir cluster index |
0x6A |
2 | VolumeFlags | Bit 0 = ActiveFAT, Bit 1 = VolumeDirty |
0x6C |
1 | BytesPerSectorShift | e.g., 9 → 512 bytes |
0x6D |
1 | SectorsPerClusterShift | e.g., 9 → 512 sectors/cluster |
0x70 |
1 | PercentInUse | 0-100, or 0xFF if unknown |
The FAT starts at FatOffset sectors and spans FatLength sectors. Each 4-byte entry corresponds to a cluster (starting at cluster index 2):
| Value | Meaning |
|---|---|
0x00000000 |
Free cluster |
0x00000002 – 0xFFFFFFF6 |
Next cluster in chain |
0xFFFFFFF7 |
Bad cluster |
0xFFFFFFFF |
End of chain |
Cluster 0 and 1 are reserved (media type descriptor and padding).
The allocation bitmap is a special file stored in the cluster heap. Its location is recorded in an Allocation Bitmap Directory Entry (entry type 0x81) in the root directory. The entry contains:
- FirstCluster (offset 20): Starting cluster of the bitmap data
- DataLength (offset 24): Bitmap size in bytes
Each bit corresponds to a cluster (bit 0 of byte 0 = cluster 2, bit 1 = cluster 3, etc.). A 1 bit means allocated; 0 means free.
The bitmap exists so the filesystem can quickly find free clusters without scanning the entire FAT. But the FAT is the authoritative record — if they disagree, the FAT wins, which is why rebuilding the bitmap from the FAT is safe.
The first 12 sectors of the volume (boot sector + extended boot sectors + OEM parameters + reserved) are protected by a checksum stored in sector 11. If you modify any of these sectors (like clearing the dirty flag in the boot sector), you must recalculate this checksum or the volume won't mount.
The checksum algorithm (from the ExFAT spec):
def vbr_checksum(sectors_data, bytes_per_sector):
"""Calculate VBR checksum over sectors 0-10."""
checksum = 0
for i in range(bytes_per_sector * 11):
# Skip VolumeFlags (bytes 106-107) and PercentInUse (byte 112)
if i == 106 or i == 107 or i == 112:
continue
checksum = ((checksum << 31) | (checksum >> 1)) + sectors_data[i]
checksum &= 0xFFFFFFFF
return checksumYour terminal doesn't have Full Disk Access. Go to System Preferences → Privacy & Security → Full Disk Access and add Terminal.app (or iTerm2, etc.). You may need to restart the terminal.
This usually means diskarbitrationd remounted the volume while you were writing. Make sure you:
- Force-unmount first:
sudo diskutil unmountDisk force diskX - Run the script immediately after unmounting
- If it persists, the USB connection may be flaky — try a different port or cable
diskarbitrationd automatically spawns fsck when it detects the volume. You can temporarily suppress auto-mounting:
# Stop disk arbitration (prevents auto-fsck on plug-in)
sudo launchctl unload /System/Library/LaunchDaemons/com.apple.diskarbitrationd.plist
# ... do your work ...
# Re-enable disk arbitration
sudo launchctl load /System/Library/LaunchDaemons/com.apple.diskarbitrationd.plistWarning: Disabling diskarbitrationd stops all automatic disk mounting system-wide. Re-enable it when done.
This means diskutil mount ran fsck and fsck still failed. Common reasons:
- Dirty flag still set — the script should clear it, but verify manually by reading offset
0x6A - VBR checksum mismatch — if you cleared the dirty flag, the checksum sector (sector 11) must be updated
- Other filesystem damage — the bitmap was the main issue, but Spotlight index files (
.Spotlight-V100) or other metadata may also be inconsistent
Workaround: Mount directly with mount_exfat (bypasses fsck):
sudo mkdir -p /Volumes/MyDrive
sudo mount_exfat /dev/diskXsY /Volumes/MyDriveThis mounts read-write. If you want to be cautious, add -o rdonly.
The mount point directory must exist. Create it first:
sudo mkdir -p /Volumes/MyDrivediskutil list externalLook for the ExFAT partition. It will be something like disk2s1 or disk4s2. Use /dev/diskXsY for mount commands and /dev/rdiskXsY for raw access (dd, Python scripts) — the r prefix gives you the raw (character) device, which is faster for sequential I/O.
The bitmap rebuild is safe because:
- It only writes the allocation bitmap and boot sector — it never touches the FAT or your actual data
- The bitmap is derived from the FAT, which is the authoritative record of cluster allocation
--dry-runmode lets you see exactly what would change before writing anything
The only risk is from the physical USB connection being unreliable during the write. Use --dry-run first to validate, then run the full repair.
Yes. ExFAT supports volumes up to 128 PB. The script handles large cluster counts and FAT sizes correctly. It was originally developed and tested on a 4 TB drive.
- Microsoft ExFAT File System Specification — the official spec
mount_exfat(8)— macOS man pagefsck_exfat(8)— macOS man page
This guide was written after recovering a 4 TB external drive that became unmountable after a macOS crash. The bitmap had 1.7 million bytes of differences — fsck_exfat couldn't fix it, but rebuilding from the FAT took under a second.