A developer Mac that has been through three Xcode major versions almost always has more iOS simulator runtimes installed than Xcode is willing to admit. The Apple Discussions thread on rogue simulators counts 18 GB of "hidden" simulators not visible in Xcode > Components and unaddressable via xcrun simctl delete unavailable. The Apple Developer Forums catalogue the same problem from the runtime side: a 9 GB orphaned runtime stuck in /System/Library/AssetsV2 that newer Xcode versions never expose in the UI. The dev who wrote up the 200 GB MacBook reclaim on brtkwr.com summarised the pattern in one sentence: "After the initial Docker/cache cleanup freed 150GB, going back and asking 'what else?' found another 75GB." Orphaned simulator runtimes are almost always part of that second pass.
What exactly is an orphaned simulator runtime?
Xcode separates two things that sound the same. A simulator device is a virtual iPhone 15 Pro or Apple Watch Series 9 you can boot. A simulator runtime is the OS image that device runs on top of, the equivalent of a guest filesystem plus kernel.
xcrun simctl is the device-level tool. xcrun simctl list shows devices. xcrun simctl delete unavailable removes phantom devices whose runtime is no longer installed. Neither of those touches the runtime image itself. The runtime images are owned by a separate piece of CoreSimulator infrastructure (xcsimruntimemgmt in Xcode 15+, MobileAsset in older Xcode) and live in a different tree.
A runtime becomes orphaned when:
- You upgraded Xcode and the new version no longer references the old runtime
- A failed runtime download left a partial DMG mount that the new Xcode does not recognise
- You ran a beta Xcode that pulled a beta runtime, then uninstalled the beta but left the runtime
- You installed runtimes via
xcodebuild -downloadPlatformand one of them never registered cleanly
In every case the runtime is on disk, eats 6 to 9 GB per iOS major, and no Xcode UI surfaces it.
Where do orphaned simulator runtimes live on a Mac?
Two trees. Knowing which one a runtime lives in changes the safe removal command.
| Path | Owner | What lives here | Safe removal |
|---|---|---|---|
/Library/Developer/CoreSimulator/Volumes/ |
xcsimruntimemgmt (Xcode 15+) |
DMG-mounted runtimes, one folder per iOS/watchOS/tvOS major | xcrun simctl runtime delete <build> or Trash move |
/System/Library/AssetsV2/com_apple_MobileAsset_iOSSimulatorRuntime/ |
MobileAsset (legacy) |
Older runtime payloads, sometimes locked by SIP | Trash move of specific subfolder, never rm -rf of the tree |
~/Library/Developer/CoreSimulator/Caches/ |
CoreSimulator |
Caches the runtimes rely on at boot | Safe to Trash, regenerates |
/Library/Developer/CoreSimulator/Profiles/Runtimes/ |
Xcode (older path) | Pre-Xcode-15 runtime bundles | Trash move of the .simruntime bundle |
The first row is where Xcode 15 and newer keeps everything. The second row is where the orphans hide because newer Xcodes stopped writing there but did not clean what older Xcodes left behind. That gap is the source of most of the 30 to 60 GB reclaim on an older dev Mac.
How do I find orphaned simulator runtimes?
A two-step audit, both read-only.
# 1. What does simctl know about?
xcrun simctl runtime list --json | python3 -m json.tool
# 2. What is actually on disk?
sudo du -sh /Library/Developer/CoreSimulator/Volumes/* 2>/dev/null
sudo du -sh /System/Library/AssetsV2/com_apple_MobileAsset_iOSSimulatorRuntime/* 2>/dev/null
Compare the two. Any path on disk that does not appear in simctl runtime list is an orphan candidate. The simctl runtime list --json output includes a build identifier for each tracked runtime. Cross-referencing builds against the .app-style folder names under Volumes/ is the explicit map.
The du step needs sudo because /Library/Developer/CoreSimulator/Volumes/ is owned by root and /System/Library/AssetsV2/ is read-only without elevation on Apple Silicon. Read-only, not writable, so du still works under sudo without risk.
How do I delete the ones simctl knows about?
If simctl runtime list shows a runtime you do not need, the one-line removal is:
# Replace <build> with the build id from `xcrun simctl runtime list`,
# for example "21E212" for iOS 17.4.
xcrun simctl runtime delete <build>
# Verify the size dropped under Volumes.
sudo du -sh /Library/Developer/CoreSimulator/Volumes/
This is the correct path for runtimes that registered cleanly. It updates xcsimruntimemgmt's index, dismounts the DMG, and removes the volume folder. It is the same code path the Platforms UI hits when you click the trash icon next to a runtime, so there is no risk a future Xcode update will refuse to recognise the install.
How do I delete the ones simctl does not know about?
This is the interesting case, the one the Apple Discussions rogue-simulator thread was about. simctl shrugs, the Platforms UI is blank, and the runtime is still on disk.
The safe answer is a Trash move of the specific runtime subfolder, not the parent tree. Identifying the subfolder is the careful step.
# Step 1: list the Volumes tree and pick the runtime folder you do not need.
sudo ls -lah /Library/Developer/CoreSimulator/Volumes/
# Step 2: confirm it is not in the simctl list. Empty output = orphan.
xcrun simctl runtime list --json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print('\n'.join(r['path'] for r in d.values()))" \
| grep "iOS_17_2_Simulator" || echo "ORPHAN, safe to remove"
# Step 3: move that specific folder to your user Trash.
sudo mv /Library/Developer/CoreSimulator/Volumes/iOS_17_2_Simulator \
~/.Trash/iOS_17_2_Simulator-orphan-$(date +%Y%m%d)
# Step 4: fix the Trash ownership so Finder can empty it.
sudo chown -R "$(whoami):staff" \
~/.Trash/iOS_17_2_Simulator-orphan-$(date +%Y%m%d)
mv to Trash beats rm -rf here for the same reason it beats rm -rf everywhere on APFS. The move is metadata-only, the rollback window is the lifetime of the Trash, and a regret restore is a Finder drag. The full reasoning lives in Move to Trash vs rm -rf for a developer Mac.
For runtimes under /System/Library/AssetsV2/, the rule is identical but the surrounding tree is more sensitive because System assets share it. Identify the specific *iOSSimulatorRuntime* subfolder, confirm its build is not in simctl runtime list, then move that subfolder, never the parent. SIP will not let you delete arbitrary children of AssetsV2, so an attempt to overreach errors out cleanly rather than damaging the system.
What about xcrun simctl delete unavailable?
That command is the right tool for a different problem: phantom devices left behind when their runtime was removed. It has nothing to do with the runtime images themselves. After deleting an orphaned runtime, devices that pointed at it become unavailable, and xcrun simctl delete unavailable is then the followup to clear those phantom rows. The full breakdown of what it does and does not touch is in the xcrun simctl delete unavailable guide, and the broader simulator reclaim playbook is in reclaim disk from Xcode simulators.
The correct order is:
- Delete the orphaned runtime image (this post)
- Run
xcrun simctl delete unavailableto clear devices that depended on it - Empty
~/Library/Developer/CoreSimulator/Caches/if disk is still tight
Skipping step 2 leaves visual noise in Xcode's device picker. Skipping step 1 wastes the reclaim that triggered the whole exercise.
How much disk does this actually reclaim?
A real audit from a 512 GB MacBook running Xcode 15.4 with a year of Xcode 14 and Xcode 13 history left over.
| Path | Size before | After cleanup | Reclaimed |
|---|---|---|---|
/Library/Developer/CoreSimulator/Volumes/iOS_15_5 |
7.2 GB | 0 | 7.2 GB |
/Library/Developer/CoreSimulator/Volumes/iOS_16_4 |
8.1 GB | 0 | 8.1 GB |
/Library/Developer/CoreSimulator/Volumes/watchOS_9_4 |
2.6 GB | 0 | 2.6 GB |
/System/Library/AssetsV2/.../iOSSimulatorRuntime/iOS_14_2 |
6.4 GB | 0 | 6.4 GB |
~/Library/Developer/CoreSimulator/Caches |
4.8 GB | 0.3 GB | 4.5 GB |
| Total | 29.1 GB | 0.3 GB | 28.8 GB |
Almost 30 GB on a single Mac, none of it visible in Xcode's UI, none of it removed by xcrun simctl delete unavailable. The pattern is identical on every long-running developer Mac the audit has been run on.
Why not script this and forget about it?
You can. A launchd job that compares simctl runtime list against /Library/Developer/CoreSimulator/Volumes/ weekly and emails the diff is a reasonable home-rolled solution. Most experienced devs do not write it because the judgement call ("is iOS 16.4 really dead to me or am I about to need it for a backport?") is hard to encode. A safer scheduled job lists candidates, surfaces last-used dates and risk labels, and stays out of the way of the actual move.
That is the same review-first principle behind every other developer cache target on a Mac. The audit is cheap. The deletion is irreversible only if you skip Trash. The judgement is yours.
If you want that same audit surfaced row by row in a UI with per-runtime size, last-modified date, Xcode-version reference, and a Move-to-Trash default, CleanMyDev is the $9.99 lifetime app that does it. It catches the orphans simctl cannot see, the legacy AssetsV2 payloads Xcode hides, and the related CoreSimulator caches that creep back after every Xcode update. No subscription, no admin password trick, no rm -rf.