- The problem. Our detector flagged 16.9 GB of iOS simulator images as abandoned. macOS won’t let anyone delete them, and Apple’s own tool left 34 GB of failed copies behind trying.
- What we found. They weren’t abandoned. They back two installed simulators the registry had lost track of, and deleting those runtimes gives back 21 MB. Only Xcode’s Components pane can remove them.
- What we built. Mint now repairs the registry before calling anything an orphan, and lists these images in Keep with a pointer to Xcode. It no longer tries the route that left 8 GB copies behind.
We shipped a detector that found 16.9 GB of abandoned iOS simulator runtimes on a developer's Mac. It even listed which builds they were.
Those files turned out to be the backing images of two simulators that were still installed, so the detector had it wrong.
This post goes through how a disk tool ends up making a call like that. Each step along the way has a trap in it, and three of those traps are still costing people disk space today. Every number below was measured on one Mac between August 19 and 21, 2026, and you can reproduce all of it.
The junk macOS will not let you delete
Every time Xcode downloads an iOS simulator, the system image lands in Apple's asset store, at /System/Library/AssetsV2/com_apple_MobileAsset_iOSSimulatorRuntime/<hash>.asset, about 8-9 GB apiece. CoreSimulator, the service that runs simulators, keeps a registry of the ones it knows about. xcrun simctl runtime list prints it.
That morning the registry printed one runtime (iOS 26.5), but the folder held three. The other two were iOS 18.2 and iOS 18.4, 8.4 and 8.5 GB, downloaded in early 2025.
Two large images, and nothing in the registry pointing at them. That's what an orphan normally looks like, and it's what our scanner concluded.
The first surprise is that you can't delete them anyway:
$ ls -lO /System/Library/AssetsV2/com_apple_MobileAsset_iOSSimulatorRuntime/
drwxr-xr-x@ 5 _nsurlsessiond wheel restricted 160 Feb 4 2025 3ee34d77….asset
drwxr-xr-x@ 5 _nsurlsessiond wheel restricted 160 Apr 8 2025 6fb1e5fe….asset
restricted is a System Integrity Protection flag. Nobody can touch the folder, sudo rm included. The usual advice online is "disable SIP, reboot, delete, re-enable SIP," and we're not going to ship that to anyone.
Apple's own runtime service can remove them, though. You ask it to adopt the image back into its registry with simctl runtime add <image> --move, then delete it by UUID. The daemon does the removal with its own entitlements, so there's no password prompt.
The refusal that costs you 8 GB
It refused to adopt the image:
$ xcrun simctl runtime add …/092-10337-196.dmg --move
SimDiskImageError code=14: Cannot copy the image because the disk is almost full
There were 24.7 GB free and the image is 8.4 GB. The daemon's log lines are redacted, so we looked inside the binary instead:
$ strings …/CoreSimulator.framework/…/simdiskimaged
minFreeSpace = %llu
Because source is on the same volume as inbox and clones are supported
will allow a lower free space limit
If the service can make an APFS clone of the source (copy-on-write, instant, zero extra bytes), it barely needs any free space. If it can't, it falls back to a full byte-for-byte copy and wants the image's size plus a large reserve on top.
It can't clone out of the SIP-protected asset store. So before Apple's own tool will help you with the folder you aren't allowed to delete, it has to make a full copy of it.
We checked this from the other direction too. A plain 9 GB file in /tmp passes the same precheck with only 14.6 GB free. Same volume, similar size, no SIP flag, and the daemon doesn't complain.
Worse, a refused runtime add isn't a no-op. By the time the daemon decides the disk is too full, it has already copied the image into its own store and registered it as an Unusable runtime, and it leaves both behind. Three refusals left three 8.38 GB copies in /Library/Developer/CoreSimulator/Cryptex/Images/, plus a 9 GB copy in its Inbox.
That's 34 GB of residue from operations that all reported failure. No tool lists it. du on your home folder never sees it, and the runtime that owns it doesn't show up in the plain simctl runtime list output.
The junk that wasn't junk
At this point our summary was that nobody could delete the 16.9 GB, and that the adoption route made things worse every time it failed. One command proved that summary wrong.
xcrun simctl runtime scan-and-mount is Apple's own repair command. It tells CoreSimulator to walk its storage and re-adopt anything it has lost track of, and it takes about a tenth of a second. After we ran it, the registry looked like this:
$ xcrun simctl runtime list
iOS 18.2 (22C150) - 87410283-… (Ready)
iOS 26.5 (23F77) - 8E56E104-… (Ready)
iOS 18.4 (22E238) - 10B15C7F-… (Ready)
Total Disk Images: 3 (24.2G)
The registry went from one runtime to three. The full JSON record for iOS 18.4 also had a field the summary listing never shows:
parentImagePath = /System/Library/AssetsV2/…/6fb1e5fe….asset/AssetData/090-44150-318.dmg
The "orphaned" asset was the parent image of an installed, working runtime. CoreSimulator had lost the registration for the two Cryptex bundles holding iOS 18.2 and 18.4, and while that registration was missing, nothing on the machine claimed the assets. The files hadn't changed. The registry had lost its record of them.
After that the cleanup was easy. Running simctl runtime delete <UUID> on the three Unusable leftovers cleared all 34 GB of staging residue. It needed no password and no staging space, and it emptied the Inbox too. The two 18.x runtimes now show up correctly in our scan: one unused, one superseded by a newer build, both deletable by UUID.
And then we were wrong again
Deleting them frees almost nothing. We ran the delete through our own executor and measured it: removing the iOS 18.2 runtime gave back 21 MB, nowhere near 8.4 GB. The registration disappeared and the bundle unmounted, but the 8.4 GB .asset stayed exactly where it was. simctl runtime delete never removes the MobileAsset it was installed from. Apple even ships a --keep-asset flag, which suggests the default does remove it, and on a Cryptex-backed runtime the flag makes no difference.
That left one way to get the space back, or so we concluded: runtime add --move, which relocates the image out of the protected store. That route can't clone, so it wants free space for the image plus about a tenth of the volume. On a 494 GB disk with 16.9 GB of dead simulators, that's roughly 58 GB you have to free up first.
Update, a week later, after we freed up that 58 GB and tried it: that route doesn't work either. The daemon adopts its copy and then prints "--move was specified but unable to remove original file: Operation not permitted". Apple's own entitled service can't unlink a file inside the sealed asset store any more than you can. Every attempt also leaves an 8 GB staging copy in CoreSimulator's Images/Inbox, and nothing we tried could remove that copy: not simctl, and not rm run as root with the administrator password typed in. No program can get back the bytes of a runtime that lives in Apple's asset store. The one place that still can is Xcode, in Settings ▸ Components, where a registered runtime has a remove button.
Looking back, the free-space math was right all along. We got the label wrong first, and then the route.
Measuring turned up two more problems. If you deleted a runtime and rescanned, it came back: our own repair step re-adopted the now-parentless asset under a fresh UUID and undid the deletion. And a delete that had worked got reported as a failure, because CoreSimulator hadn't republished its registry within the 15 seconds we waited. Both are fixed. We only found them once we started measuring the filesystem instead of trusting the registry.
One more Apple quirk if you're writing your own cleanup: simctl runtime delete --notUsedSinceDays 90 matched nothing here, even though both 18.x runtimes have lastUsedAt set to null. Apple's "delete what you haven't used" filter skips runtimes that have never been used at all.
One folder, two names
While we were building the chart that shows where disk space goes, an internal check started firing. The children of the Developer category added up to 18.1 GB more than the category itself. That shouldn't be possible, since every child is measured from inside its parent. The check fired 502 times, and the extra was always exactly 18.1 GB.
The cause was a firmlink. Since Catalina, macOS keeps the system on a sealed read-only volume and your data on another, then stitches them together so the filesystem looks like one tree. Some paths end up with two spellings that point to the same directory:
$ stat -f '%d %i %N' /System/Library/AssetsV2 /System/Volumes/Data/System/Library/AssetsV2
16777233 210916113 /System/Library/AssetsV2
16777233 210916113 /System/Volumes/Data/System/Library/AssetsV2
Both lines show the same device and the same inode under two names. Our whole-disk pass recorded the simulator folder under one spelling and the scanner reported the runtimes under the other. A string comparison called those different places, so 18.1 GB got added on top of a category that already included it.
Every disk tool matches up measurements by path, and any tool that does will have this bug unless it compares through the firmlink table. The underlying problem is older than APFS. Two different paths can be the same file, so compare device and inode, not strings.
The 46.7 GB that was never there
Run du on Chrome's update leftovers and it reports 46.7 GB across 34 directories named *.code_sign_clone. Delete all of them and your free space doesn't change at all.
They're APFS clones. During code signing, Chrome makes a copy-on-write snapshot of its own bundle, and the clone shares every block with the original until one of them is modified, which never happens. du reports logical size, so it counts a 1.4 GB application 34 times.
A cleaner that ranks by du puts these at the top of the list and lets you "free" 46.7 GB, and afterwards your free space reads the same as before. We measure allocated blocks instead and leave clone directories out of the overview. We'd rather not mention them than promise 46.7 GB and free nothing.
The delete that arrived a second late
The last one is small. It bit us on the day we wrote this.
At the time, our receipt after a clean showed the change in free space observed on the volume, sampled right after the last file went away, instead of the size of what was removed. Free space is the only number a user can check against Finder, which is why we did it that way, and it had worked for months.
Then someone deleted two 9 GB folders and the receipt said freedBytes: 0. The files were gone and the history entry recorded 17.5 GB permanently deleted, but the volume's free space, read 375 milliseconds later, hadn't moved.
APFS reclaims the blocks of a large tree asynchronously. A few hundred megabytes come back almost at once; tens of gigabytes take seconds, and a sample taken inside that gap sees nothing. The headline number at the top of the window read the value only once, so it didn't move either. To the user, that looks exactly like a delete that failed.
The fix is boring. We sample for longer when more bytes were processed, stop once the observed change stops growing, and re-read the headline on a short schedule after anything that frees space, whichever button started it.
What we changed
We kept the detector, but it can no longer flag anything based on a registry it hasn't verified. Before Mint calls any simulator storage an orphan, it asks CoreSimulator to re-adopt its own storage and then reads the registry again. On this Mac, that correctly identifies the two "orphaned" images as the backing images of two installed runtimes. Mint lists them in Keep, with a note pointing to Xcode's Components pane, the one place that can remove them. A button in Mint would have freed 21 MB and reported the rest as "nothing was removed".
Mint no longer tries the runtime add --move route, so it can't leave 8 GB of staging copies behind. Copies that CoreSimulator already lists as Unusable are deleted by UUID, and a staging copy left in the Inbox gets one attempt with the administrator password. If a removal is refused even with that password, Mint remembers and stops offering the row until the file or the OS changes.
None of these traps are bugs in macOS. Sealed system volumes, firmlinks, copy-on-write clones, lazy reclamation and a self-healing runtime registry all make sense as engineering, and you never notice them when you're just using the machine. They cause trouble for a program that claims to know where your space went, because each one opens a gap between how big a folder is and how much space deleting it gives back.
The cheap way to build a disk cleaner is to walk the tree, sort by size, and put checkboxes next to the big things. With every trap above, that approach shows a bigger number than the user will get back, and they only find out after they click.
Our most expensive mistake had nothing to do with measuring: we trusted the registry when it said nothing referenced those images, and never checked whether it had finished counting.
We took all measurements with Mint's read-only scan hooks on one developer Mac in August 2026, and you can reproduce them on yours. For what coding agents leave behind, see Your Coding Agent Is Eating Your Disk.
Apply this to your Mac
For the steps that work today, read the iOS Simulator storage guide. It separates runtimes, device data and caches. If your large folder is DerivedData, read the Xcode build-output guide instead: that's different data, and removing it has different consequences.