<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Xcode · Loosely Decoupled</title>
    <link>https://looselydecoupled.com/topics/xcode/</link>
    <description>Answers to PowerShell, Windows Server and OpenSSH questions the docs skip, with the reasoning behind them. Answer first, discussion after.</description>
    <language>en-us</language>
    
    <copyright>© 2026 Emmanuel Tsouris</copyright>
    <lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://looselydecoupled.com/topics/xcode/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>StoreKit Purchase Verifies but currentEntitlements Is Empty in Xcode Testing</title>
      <link>https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/</link>
      <guid isPermaLink="true">https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/</guid>
      <pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Emmanuel Tsouris</dc:creator>
      <category>Xcode</category>
      <category>Storekit</category>
      
      <description>StoreKit itself returned nothing, not the verification code, and deleting and reinstalling the app cleared what a new .storekit file didn&#39;t.</description>
      <content:encoded><![CDATA[<div class="answer" data-answer="true">
  <p class="answer__label" data-pagefind-ignore>TL;DR</p>
  <p>A StoreKit test purchase on a physical device can succeed and verify while <code>Transaction.currentEntitlements</code> comes back empty right after. First find out whether StoreKit returned nothing or your code dropped the result: count every result before you verify it. If the count is zero, delete the app from the device, reinstall it from Xcode and buy again. In the one case tested here, on one iPhone, that cleared it.</p>
<div class="code-block" data-copyable data-copy-label="Swift">
  <div class="code-block__head"><span class="code-block__lang">Swift</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">await</span> <span class="n">result</span> <span class="k">in</span> <span class="n">Transaction</span><span class="p">.</span><span class="n">currentEntitlements</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entitlementCount</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">guard</span> <span class="kd">let</span> <span class="nv">transaction</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">checkVerified</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">// The app&#39;s log line also prints the Pro state after this count.</span>
</span></span><span class="line"><span class="cl"><span class="bp">print</span><span class="p">(</span><span class="s">&#34;StoreManager: Entitlements refreshed - </span><span class="si">\(</span><span class="n">entitlementCount</span><span class="si">)</span><span class="s"> from StoreKit&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
  </div>
</div><div class="tested" id="tested">
  <p class="label label--seam">Tested with</p><dl class="tested__rows">
    <div class="tested__row"><dt>iOS</dt> <dd>27.0.1, iPhone</dd></div>
    <div class="tested__row"><dt>Xcode</dt> <dd>27.1 (27A9275)</dd></div>
    <div class="tested__row"><dt>Framework</dt> <dd>StoreKit 2, local .storekit configuration</dd></div>
    <div class="tested__row"><dt>Date</dt> <dd>October 2026</dd></div>
  </dl>
</div>

</div>

<p>I hit this while preparing a release of a small iPhone app I&rsquo;m building, which sells one non-consumable Pro upgrade. The purchase sheet said &ldquo;You&rsquo;re all set&rdquo;, and the app stayed locked. This page covers how I separated StoreKit from my own code, what turned out not to matter, and the reinstall that fixed it.</p>
<h2 id="purchase-succeeds-but-the-upgrade-stays-locked">Purchase succeeds, but the upgrade stays locked<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#purchase-succeeds-but-the-upgrade-stays-locked"
     aria-label="Link to section: Purchase succeeds, but the upgrade stays locked" data-pagefind-ignore></a>
</h2>
<p>On a physical iPhone launched from Xcode with a StoreKit configuration file, the symptoms, in the order they appear:</p>
<ol>
<li>The purchase sheet said &ldquo;You&rsquo;re all set&rdquo;, and the app logged a verified, unrevoked transaction.</li>
<li>The entitlement refresh straight after it found nothing.</li>
<li>Xcode&rsquo;s transaction manager, under <strong>Debug</strong> &gt; <strong>StoreKit</strong> &gt; <strong>Manage Transactions</strong>, listed the purchase.</li>
<li>Restore Purchases reported no previous purchases.</li>
<li>Buying again failed with <code>ASDServerErrorDomain 2010</code>.</li>
</ol>
<p>The two log lines from steps 1 and 2:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="Text">
  <div class="code-block__head"><span class="code-block__lang">Text</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">StoreManager: Purchase verified - &lt;product-id&gt;, revoked: false
</span></span><span class="line"><span class="cl">StoreManager: Entitlements refreshed - 0 from StoreKit, Pro: false</span></span></code></pre></div></div>
  </div>
</div><p>I don&rsquo;t know what <code>ASDServerErrorDomain 2010</code> means. I couldn&rsquo;t find it in Apple&rsquo;s documentation, so treat it as a symptom here, not a diagnosis.</p>
<h2 id="count-entitlements-before-you-verify-them">Count entitlements before you verify them<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#count-entitlements-before-you-verify-them"
     aria-label="Link to section: Count entitlements before you verify them" data-pagefind-ignore></a>
</h2>
<p>An empty entitlement list can come from two places: StoreKit emitted nothing, or your code discarded what it emitted. A loop that only counts the transactions it keeps can&rsquo;t tell those apart. A failed verification, a revocation check or a product ID filter all look the same as an empty sequence.</p>
<p>Count each result as it arrives, before any check, as in the code in the answer. In that code, <code>checkVerified</code> is the app&rsquo;s own verification helper, not a StoreKit API. If that count is zero, the sequence itself was empty and your filtering isn&rsquo;t the cause. Mine was zero.</p>
<p>The product type matters too, because <code>currentEntitlements</code> doesn&rsquo;t include every product type. Apple&rsquo;s <a href="https://developer.apple.com/documentation/storekit/transaction/currententitlements" rel="external noopener" target="_blank"><code>currentEntitlements</code> reference<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> says it emits &ldquo;a transaction for each non-consumable Apple In-App Purchase&rdquo; and leaves out consumables and refunded or revoked products. Check the product&rsquo;s type in the <code>.storekit</code> file. Mine was <code>NonConsumable</code>, so it should have appeared.</p>
<h2 id="placeholder-team-id-wasnt-the-cause">Placeholder team ID wasn&rsquo;t the cause<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#placeholder-team-id-wasnt-the-cause"
     aria-label="Link to section: Placeholder team ID wasn’t the cause" data-pagefind-ignore></a>
</h2>
<p>The <code>.storekit</code> file was an older one, written by hand. Its <code>_developerTeamID</code> was the placeholder <code>YOUR_TEAM_ID</code>, and its product and app IDs didn&rsquo;t match App Store Connect. <strong>Sync with App Store Connect</strong> failed with &ldquo;Could not sign in&rdquo;, even though Xcode was signed in to an Admin account.</p>
<p>That looked like the cause, and fixing it was still worth doing. Apple&rsquo;s <a href="https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode" rel="external noopener" target="_blank">StoreKit testing setup guide<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> has you pick the team and app when you create a synced configuration file. To sync a converted file again, it says to create a new synced file. I replaced the hand-written file with a new synced one. It wasn&rsquo;t enough on its own. A purchase made after the swap still left the entitlement count at zero.</p>
<h2 id="entitlement-count-by-environment">Entitlement count by environment<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#entitlement-count-by-environment"
     aria-label="Link to section: Entitlement count by environment" data-pagefind-ignore></a>
</h2>
<p>Each run used the same build of the app:</p>
<div class="table-wrap" data-scrollable data-label="Table: Environment">
  <table>
    <thead>
      <tr><th scope="col">Environment</th><th scope="col">Entitlements after purchase</th></tr>
    </thead>
    <tbody>
      <tr><td>Simulator, iOS 26.5, synced <code>.storekit</code> file</td><td>1</td></tr>
      <tr><td>iPhone, iOS 27.0.1, synced <code>.storekit</code> file, same install as before the swap</td><td>0</td></tr>
      <tr><td>iPhone, iOS 27.0.1, App Store sandbox, scheme with no StoreKit configuration, fresh install</td><td>1</td></tr>
      <tr><td>iPhone, iOS 27.0.1, synced <code>.storekit</code> file, after deleting and reinstalling the app</td><td>1</td></tr>
    </tbody>
  </table>
</div>
<p>The Simulator result alone didn&rsquo;t separate the device from the OS version, because the Simulator ran an older iOS. The sandbox row answers a narrower question. On iOS 27, against the App Store sandbox, a fresh install logged <code>Entitlements refreshed - 1 from StoreKit, Pro: true</code>. Because that install was fresh too, it doesn&rsquo;t show whether the sandbox would have returned the entitlement on the install that returned zero. It shows only that a sandbox purchase unlocks on a clean install, in one run.</p>
<h2 id="delete-the-app-and-test-again">Delete the app and test again<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#delete-the-app-and-test-again"
     aria-label="Link to section: Delete the app and test again" data-pagefind-ignore></a>
</h2>
<p>The last row of the table is the fix. On the one iPhone and iOS build tested, the empty result behaved like test state stored on the device: it survived replacing the configuration file, and deleting the app cleared it. I captured only the before and after, so what that state was, what put it there, and why replacing the file didn&rsquo;t reset it are unknown.</p>
<p>To clear it:</p>
<ol>
<li>On the iPhone, delete the app.</li>
<li>In Xcode, run the scheme that has the StoreKit configuration selected.</li>
<li>In the app, tap <strong>Restore Purchases</strong> before buying anything. With nothing bought, it reports no previous purchases.</li>
<li>Buy the product again.</li>
</ol>
<p>Check the console. A purchase that unlocks logs a verified transaction followed by a count of one:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="Text">
  <div class="code-block__head"><span class="code-block__lang">Text</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">StoreManager: Purchase verified - &lt;product-id&gt;, revoked: false
</span></span><span class="line"><span class="cl">StoreManager: Entitlements refreshed - 1 from StoreKit, Pro: true</span></span></code></pre></div></div>
  </div>
</div><h2 id="resets-that-dont-reach-the-device">Resets that don&rsquo;t reach the device<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#resets-that-dont-reach-the-device"
     aria-label="Link to section: Resets that don’t reach the device" data-pagefind-ignore></a>
</h2>
<p><code>xcrun simctl erase all</code> erases every simulator. It doesn&rsquo;t touch a physical device. For a device, Apple&rsquo;s <a href="https://developer.apple.com/documentation/storekit/testing-in-app-purchases-in-xcode" rel="external noopener" target="_blank">guide to testing in-app purchases in Xcode<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> resets a test by deleting the transaction under <strong>Debug</strong> &gt; <strong>StoreKit</strong> &gt; <strong>Manage Transactions</strong>. Whether that reset clears the same state isn&rsquo;t recorded here; deleting the app is what cleared it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Which Xcode Simulators, Runtimes and DeviceSupport Folders Can You Delete?</title>
      <link>https://looselydecoupled.com/xcode-storage-simulators-device-support/</link>
      <guid isPermaLink="true">https://looselydecoupled.com/xcode-storage-simulators-device-support/</guid>
      <pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Emmanuel Tsouris</dc:creator>
      <category>macOS</category>
      <category>Xcode</category>
      
      <description>You measure Xcode&#39;s simulators, runtimes and DeviceSupport and delete what nothing uses, including the runtime images a du of your home folder misses.</description>
      <content:encoded><![CDATA[<div class="answer" data-answer="true">
  <p class="answer__label" data-pagefind-ignore>TL;DR</p>
  <p>Xcode keeps simulator devices, simulator runtimes and DeviceSupport (debug symbols copied from your physical devices) in three separate places, and one <code>du</code> total hides which is big. Measure each on its own. Skip <code>du</code> on <code>/Library/Developer/CoreSimulator/Volumes</code>: it walks into the mounted runtime images and reports their unpacked contents, not the disk they use.</p>
<p>Delete only what no test target or connected device uses:</p>
<ul>
<li>A simulator device: <code>xcrun simctl delete &lt;UDID&gt;</code>.</li>
<li>A simulator runtime: <code>xcrun simctl runtime delete</code>, with <code>--dry-run</code> first.</li>
<li>A DeviceSupport folder: move it to the Trash.</li>
</ul>
<p>Compare Container Free Space before and after. A deleted runtime can leave the list and still hold its space, so reread later.</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">~/Library/Developer/CoreSimulator/Devices</span>
</span><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">simctl</span> <span class="console__token">runtime</span> <span class="console__token">list</span>
</span><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">/System/Library/AssetsV2/com_apple_MobileAsset_*SimulatorRuntime</span>
</span><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">~/Library/Developer/Xcode/*DeviceSupport</span>
</span><span class="line"><span class="prompt">$ </span><span class="console__token">diskutil</span> <span class="console__token">info</span> <span class="console__token">/</span> <span class="console__token">|</span> <span class="console__token">grep</span> <span class="console__token">&#34;Container</span> <span class="console__token">Free</span> <span class="console__token">Space&#34;</span></span></code></pre>
    </div>
  </div></div><div class="tested" id="tested">
  <p class="label label--seam">Tested with</p><dl class="tested__rows">
    <div class="tested__row"><dt>macOS</dt> <dd>27.0.1, Apple silicon</dd></div>
    <div class="tested__row"><dt>Xcode</dt> <dd>27.0 (27A266a)</dd></div>
    <div class="tested__row"><dt>Date</dt> <dd>October 2026</dd></div>
  </dl>
</div>

</div>

<p>A <code>du</code> of the home folder, as in <a href="https://looselydecoupled.com/mac-disk-full-developer-caches/">What&rsquo;s Taking Up All the Space on My Developer Mac?</a>, shows two of Xcode&rsquo;s three places. The third, simulator runtimes, lives outside the home folder. To get a real before-and-after without touching anything I use, I installed a watchOS 27.0 runtime for this page and removed it again. This is part of a series on <a href="https://looselydecoupled.com/series/developer-mac-disk-cleanup/">cleaning up a developer Mac&rsquo;s disk</a>, and each part stands alone.</p>
<h2 id="check-how-much-space-simulators-and-devicesupport-use">Check how much space simulators and <code>DeviceSupport</code> use<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#check-how-much-space-simulators-and-devicesupport-use"
     aria-label="Link to section: Check how much space simulators and DeviceSupport use" data-pagefind-ignore></a>
</h2>
<p>Xcode keeps simulator devices, simulator runtimes and DeviceSupport in three places, and each holds something different:</p>
<div class="table-wrap" data-scrollable data-label="Table: Item">
  <table>
    <thead>
      <tr><th scope="col">Item</th><th scope="col">What it holds</th><th scope="col">Where</th></tr>
    </thead>
    <tbody>
      <tr><td>Simulator devices</td><td>Each simulated iPhone, iPad or Apple TV, with its apps and data</td><td><code>~/Library/Developer/CoreSimulator/Devices</code></td></tr>
      <tr><td>Simulator runtimes</td><td>The OS images the devices boot</td><td>Outside your home folder, under <code>/System/Library/AssetsV2</code></td></tr>
      <tr><td>DeviceSupport</td><td>Debug symbols copied from physical devices you connected</td><td><code>~/Library/Developer/Xcode/iOS DeviceSupport</code> and <code>tvOS DeviceSupport</code></td></tr>
    </tbody>
  </table>
</div>
<p>Record the free space first, so you can check the result later:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">diskutil</span> <span class="console__token">info</span> <span class="console__token">/</span> <span class="console__token">|</span> <span class="console__token">grep</span> <span class="console__token">&#34;Container</span> <span class="console__token">Free</span> <span class="console__token">Space&#34;</span></span></code></pre>
    </div>
  </div></div><p>Then measure the folders. These commands only read:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">~/Library/Developer/CoreSimulator/Devices</span>
</span><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">~/Library/Developer/Xcode/*DeviceSupport</span>
</span><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">~/Library/Developer/Xcode/DerivedData</span> <span class="console__token">~/Library/Developer/Xcode/Archives</span></span></code></pre>
    </div>
  </div></div><p><code>du -h</code> and <code>simctl</code> print binary units, so every G they print on this page is GiB; <code>diskutil</code> prints decimal GB. On my Mac, devices were 44 GiB, <code>iOS DeviceSupport</code> 35 GiB and <code>tvOS DeviceSupport</code> 6.6 GiB. DerivedData was 0B and Archives 10M, so those weren&rsquo;t the problem here; check yours, because on a Mac that builds a lot they can be. The runtimes totaled 22.3G by <code>simctl</code>&rsquo;s count, which the next section shows how to read. Devices, both DeviceSupport folders and runtimes came to about 108 GiB.</p>
<h2 id="find-where-simulator-runtimes-live-and-size-them">Find where simulator runtimes live and size them<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#find-where-simulator-runtimes-live-and-size-them"
     aria-label="Link to section: Find where simulator runtimes live and size them" data-pagefind-ignore></a>
</h2>
<p>Runtimes are disk images, and macOS mounts them read-only. List them:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">simctl</span> <span class="console__token">runtime</span> <span class="console__token">list</span></span></code></pre>
    </div>
  </div><div class="code-block code-block--wrap console__output">
    <p class="console__output-label">Output</p>
    <div class="code-block__scroll"><pre><code><span class="line"><span class="console__token">==</span> <span class="console__token">Disk</span> <span class="console__token">Images</span> <span class="console__token">==</span>
</span><span class="line"><span class="console__token">--</span> <span class="console__token">iOS</span> <span class="console__token">--</span>
</span><span class="line"><span class="console__token">iOS</span> <span class="console__token">27.0</span> <span class="console__token">(24A434)</span> <span class="console__token">-</span> <span class="console__token">&lt;runtime-id&gt;</span> <span class="console__token">(Ready)</span>
</span><span class="line"><span class="console__token">iOS</span> <span class="console__token">26.5</span> <span class="console__token">(23F77)</span> <span class="console__token">-</span> <span class="console__token">&lt;runtime-id&gt;</span> <span class="console__token">(Ready)</span>
</span><span class="line"><span class="console__token">--</span> <span class="console__token">tvOS</span> <span class="console__token">--</span>
</span><span class="line"><span class="console__token">tvOS</span> <span class="console__token">27.0</span> <span class="console__token">(24J360)</span> <span class="console__token">-</span> <span class="console__token">&lt;runtime-id&gt;</span> <span class="console__token">(Ready)</span>
</span><span class="line"><span class="console__token">tvOS</span> <span class="console__token">26.5</span> <span class="console__token">(23L470)</span> <span class="console__token">-</span> <span class="console__token">&lt;runtime-id&gt;</span> <span class="console__token">(Ready)</span>
</span><span class="line">
</span><span class="line"><span class="console__token">Total</span> <span class="console__token">Disk</span> <span class="console__token">Images:</span> <span class="console__token">4</span> <span class="console__token">(22.3G)</span></span></code></pre>
    </div>
  </div></div><p>Run it again with <code>-v</code> for each image&rsquo;s details. The <code>-v</code> output adds a <code>Last Used At</code> line per image. It also gives an <code>Image Path</code> under <code>/System/Library/AssetsV2/com_apple_MobileAsset_*SimulatorRuntime/</code> and a <code>Mount Path</code>: under <code>/Library/Developer/CoreSimulator/Volumes/</code> for the iOS and tvOS images here, and under <code>/private/var/run/com.apple.security.cryptexd/mnt/</code> for the watchOS one. The image path is where the bytes are, so size the runtime images there:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">/System/Library/AssetsV2/com_apple_MobileAsset_*SimulatorRuntime</span></span></code></pre>
    </div>
  </div></div><p>Here that printed 15G for the iOS runtime images and 6.9G for the tvOS ones. These figures are rounded and come from a different tool than the 22.3G total above. Because the images live outside your home folder, a <code>du</code> of <code>~</code> never counts them.</p>
<p>Do not run <code>du</code> on a mount path. <code>du -sh /Library/Developer/CoreSimulator/Volumes</code> printed 48G on this Mac, but that isn&rsquo;t disk use. <code>du</code> walks into each mounted image and counts its unpacked contents, which are already counted in the runtime images. Adding it to the totals counts the runtimes twice, and then some.</p>
<h2 id="delete-simulator-devices-you-no-longer-use">Delete simulator devices you no longer use<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#delete-simulator-devices-you-no-longer-use"
     aria-label="Link to section: Delete simulator devices you no longer use" data-pagefind-ignore></a>
</h2>
<p>List the devices, grouped by runtime:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">simctl</span> <span class="console__token">list</span> <span class="console__token">devices</span></span></code></pre>
    </div>
  </div></div><p>I had 27: 11 each on iOS 26.5 and iOS 27.0, 3 on tvOS 26.5 and 2 on tvOS 27.0. A device is worth keeping if you run a test or a preview on it. To see which ones are large, size each device folder; the folder name is the device&rsquo;s UDID, which the list prints in parentheses:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">du</span> <span class="console__token">-sh</span> <span class="console__token">~/Library/Developer/CoreSimulator/Devices/*</span> <span class="console__token">|</span> <span class="console__token">sort</span> <span class="console__token">-hr</span> <span class="console__token">|</span> <span class="console__token">head</span></span></code></pre>
    </div>
  </div></div><p>By runtime, the iOS 26.5 devices took 19.9 GiB, iOS 27.0 19.1 GiB, tvOS 26.5 3.6 GiB and tvOS 27.0 1.3 GiB. The largest single device was 5.0 GiB. For scale, one watchOS device I booted for about 80 seconds grew from 8 KiB to 546 MiB. If you no longer test on a runtime, its devices are the candidates, and so is the runtime itself.</p>
<p>Deleting a device removes its installed apps and data. Two commands delete devices. <code>xcrun simctl delete &lt;UDID&gt;</code> removes the devices you name. Use the UDID, not the name, because the same device name appears under every runtime. There&rsquo;s no undo, so copy out any test data you want first. <code>xcrun simctl delete unavailable</code> removes devices that the current Xcode SDK doesn&rsquo;t support, according to <code>xcrun simctl help delete</code>. Check what it would remove before you run it:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">simctl</span> <span class="console__token">list</span> <span class="console__token">devices</span> <span class="console__token">unavailable</span></span></code></pre>
    </div>
  </div><div class="code-block code-block--wrap console__output">
    <p class="console__output-label">Output</p>
    <div class="code-block__scroll"><pre><code><span class="line"><span class="console__token">==</span> <span class="console__token">Devices</span> <span class="console__token">==</span>
</span><span class="line"><span class="console__token">--</span> <span class="console__token">iOS</span> <span class="console__token">26.5</span> <span class="console__token">--</span>
</span><span class="line"><span class="console__token">--</span> <span class="console__token">iOS</span> <span class="console__token">27.0</span> <span class="console__token">--</span>
</span><span class="line"><span class="console__token">--</span> <span class="console__token">tvOS</span> <span class="console__token">26.5</span> <span class="console__token">--</span>
</span><span class="line"><span class="console__token">--</span> <span class="console__token">tvOS</span> <span class="console__token">27.0</span> <span class="console__token">--</span></span></code></pre>
    </div>
  </div></div><p>If every group is empty, as here, <code>delete unavailable</code> removes nothing.</p>
<h2 id="delete-simulator-runtimes-you-no-longer-use">Delete simulator runtimes you no longer use<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#delete-simulator-runtimes-you-no-longer-use"
     aria-label="Link to section: Delete simulator runtimes you no longer use" data-pagefind-ignore></a>
</h2>
<p>A runtime is in use if a test target needs that OS version or a device you keep still runs on it. <code>Last Used At</code> in <code>xcrun simctl runtime list -v</code> shows when each image was last used.</p>
<p><code>xcrun simctl runtime delete</code> removes one. It takes an identifier from the list, or a filter: <code>--notUsedSinceDays &lt;days&gt;</code>, <code>--unusable</code> or <code>--outdated</code>. Add <code>--dry-run</code> and it prints what it would delete and deletes nothing:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">simctl</span> <span class="console__token">runtime</span> <span class="console__token">delete</span> <span class="console__token">--notUsedSinceDays</span> <span class="console__token">90</span> <span class="console__token">--dry-run</span></span></code></pre>
    </div>
  </div></div><p>When the dry run lists what you expect, run the same command without <code>--dry-run</code>, or pass an identifier from <code>xcrun simctl runtime list</code> to remove a single image. Read the whole dry-run list first: a filter can match several runtimes, and each one is a multi-GB download if you need that OS version again. <code>xcrun simctl help runtime</code> says the delete shuts down any booted simulators and unmounts the image first. In my run, a tvOS simulator booted on a different runtime stayed booted, so I can&rsquo;t say which ones it stops. Shut down any simulator holding state you care about first.</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">simctl</span> <span class="console__token">runtime</span> <span class="console__token">delete</span> <span class="console__token">&lt;identifier&gt;</span></span></code></pre>
    </div>
  </div></div><p>Use this command, not <code>rm</code>. The images are mounted and managed by the system, so they aren&rsquo;t folders to remove by hand. To get a runtime back, run <code>xcodebuild -downloadPlatform &lt;iOS|watchOS|tvOS|visionOS&gt;</code>, as in Apple&rsquo;s <a href="https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes" rel="external noopener" target="_blank">Installing additional simulator runtimes<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a>.</p>
<h2 id="why-a-deleted-simulator-runtime-can-keep-its-disk-space">Why a deleted simulator runtime can keep its disk space<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#why-a-deleted-simulator-runtime-can-keep-its-disk-space"
     aria-label="Link to section: Why a deleted simulator runtime can keep its disk space" data-pagefind-ignore></a>
</h2>
<p>A runtime can leave <code>xcrun simctl runtime list</code> while its image stays mounted and keeps its space. Do not read the runtime list as proof you got the space back. Check that the image has left <code>mount</code> and that Container Free Space has risen, and if it hasn&rsquo;t, reread it later and after a restart.</p>
<p>The watchOS 27.0 runtime I installed for this page was a 3.83 GB download; <code>xcrun simctl runtime list -v</code> sized it at 3.6G (GiB), and it cut Container Free Space from 83.9 GB to 80.1 GB. After a dry run that named only that runtime, <code>xcrun simctl runtime delete &lt;identifier&gt;</code> took it off the list within five minutes, but Container Free Space didn&rsquo;t rise by those 3.8 GB in the next 21 minutes. The backing folder under <code>/System/Library/AssetsV2</code> still measured 3,736,360 KiB (3.56 GiB), and the image was still in <code>mount</code>, held by a <code>SimLaunchHost.arm64</code> process that had a library from it mapped. <code>xcrun simctl shutdown all</code> didn&rsquo;t release it. Stopping that process left nothing holding the image, but the image stayed mounted and the space stayed in use for the nine minutes I watched. I didn&rsquo;t test a restart, unmounting the image or restarting the CoreSimulator service, so I can&rsquo;t say what finally frees it.</p>
<h2 id="delete-devicesupport-folders-you-no-longer-need">Delete <code>DeviceSupport</code> folders you no longer need<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#delete-devicesupport-folders-you-no-longer-need"
     aria-label="Link to section: Delete DeviceSupport folders you no longer need" data-pagefind-ignore></a>
</h2>
<p>DeviceSupport holds debug symbols copied from physical devices you connected. Each folder is named for the device model, the OS version and the build, and there&rsquo;s one per combination. My <code>iOS DeviceSupport</code> had six folders, at 5.4G to 6.6G each, and <code>tvOS DeviceSupport</code> had two, at 3.4G and 3.2G.</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">ls</span> <span class="console__token">~/Library/Developer/Xcode/iOS\</span> <span class="console__token">DeviceSupport</span></span></code></pre>
    </div>
  </div><div class="code-block code-block--wrap console__output">
    <p class="console__output-label">Output</p>
    <div class="code-block__scroll"><pre><code><span class="line"><span class="console__token">&lt;model&gt;</span> <span class="console__token">26.6</span> <span class="console__token">(&lt;build&gt;)</span>
</span><span class="line"><span class="console__token">&lt;model&gt;</span> <span class="console__token">27.0</span> <span class="console__token">(&lt;build&gt;)</span></span></code></pre>
    </div>
  </div></div><p>A folder is needed while a connected device still runs that OS version, or while you still read crash reports from that version. Xcode uses these symbols to name the system framework frames in a report, and Apple&rsquo;s <a href="https://developer.apple.com/documentation/xcode/adding-identifiable-symbol-names-to-a-crash-report" rel="external noopener" target="_blank">crash report documentation<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> says to download the device symbols for the report&rsquo;s OS version if it can&rsquo;t. List your connected and paired devices from the terminal, then check each one&rsquo;s OS version on the device itself:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">xcrun</span> <span class="console__token">devicectl</span> <span class="console__token">list</span> <span class="console__token">devices</span></span></code></pre>
    </div>
  </div></div><p>Any folder for a version no device runs now, and that no crash report you still read comes from, is a candidate. If every device you connect is on 27, that&rsquo;s the five iOS 26.x folders (28.1 GiB) and both tvOS 26.6 folders (6.6 GiB). I kept all of mine, because I still need 26.x.</p>
<p><code>simctl</code> doesn&rsquo;t manage these; they&rsquo;re plain folders. Move the one you chose to the Trash in Finder, which leaves you an undo, and empty the Trash after you confirm Xcode still works. Once it&rsquo;s gone, a crash report from a device you no longer have can&rsquo;t be symbolicated from it. I haven&rsquo;t tested what Xcode does the next time you connect a device whose folder you removed, so remove one and check before you clear several.</p>
<h2 id="check-how-much-space-you-got-back">Check how much space you got back<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#check-how-much-space-you-got-back"
     aria-label="Link to section: Check how much space you got back" data-pagefind-ignore></a>
</h2>
<p>Run the same command you started with:</p>
<div class="console"><div class="code-block code-block--wrap console__command" data-copyable data-copy-label="command">
    <div class="code-block__head"><span class="code-block__lang">Command</span></div>
    <div class="code-block__scroll"><pre data-copy-source><code><span class="line"><span class="prompt">$ </span><span class="console__token">diskutil</span> <span class="console__token">info</span> <span class="console__token">/</span> <span class="console__token">|</span> <span class="console__token">grep</span> <span class="console__token">&#34;Container</span> <span class="console__token">Free</span> <span class="console__token">Space&#34;</span></span></code></pre>
    </div>
  </div></div><p>Mine read 83.9 GB before the watchOS run. Other work on the Mac moves this figure by up to about 1 GB with no action of yours, so a change under that is noise. Reread after a restart if a runtime delete hasn&rsquo;t returned its space. Do not expect the change to match the <code>du</code> totals. Only the container&rsquo;s figure says what the Mac got back.</p>
<h2 id="do-this-with-a-coding-agent">Do this with a coding agent<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#do-this-with-a-coding-agent"
     aria-label="Link to section: Do this with a coding agent" data-pagefind-ignore></a>
</h2>
<p>Paste this prompt into your own local coding agent. It measures and recommends, and it removes nothing until you say what to remove:</p>
<div class="code-block" data-copyable data-copy-label="Text">
  <div class="code-block__head"><span class="code-block__lang">Text</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Measure Xcode&#39;s disk use on this Mac. Report only; delete nothing until I
</span></span><span class="line"><span class="cl">name what to remove.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1. Run diskutil info / and record Container Free Space.
</span></span><span class="line"><span class="cl">2. Measure three places separately: simulator devices
</span></span><span class="line"><span class="cl">   (~/Library/Developer/CoreSimulator/Devices), simulator runtimes, and
</span></span><span class="line"><span class="cl">   DeviceSupport (~/Library/Developer/Xcode/*DeviceSupport). For runtimes,
</span></span><span class="line"><span class="cl">   use xcrun simctl runtime list -v and measure each runtime image
</span></span><span class="line"><span class="cl">   under /System/Library/AssetsV2. Do not run du on
</span></span><span class="line"><span class="cl">   /Library/Developer/CoreSimulator/Volumes; it walks into the mounted
</span></span><span class="line"><span class="cl">   images and overstates the size.
</span></span><span class="line"><span class="cl">3. For devices, list them with xcrun simctl list devices, group them by
</span></span><span class="line"><span class="cl">   runtime and report the size of each group. Also run
</span></span><span class="line"><span class="cl">   xcrun simctl list devices unavailable.
</span></span><span class="line"><span class="cl">4. List my connected devices (xcrun devicectl list devices) and ask me for
</span></span><span class="line"><span class="cl">   the OS version of each; do not infer it. Also report which OS versions my test targets and deployment targets
</span></span><span class="line"><span class="cl">   need, from the projects I name or from the project files you can find.
</span></span><span class="line"><span class="cl">5. Recommend removal candidates with a reason for each: devices and runtimes
</span></span><span class="line"><span class="cl">   no target uses, and DeviceSupport folders for OS versions no connected
</span></span><span class="line"><span class="cl">   device runs. Ask me whether I still read crash reports from those
</span></span><span class="line"><span class="cl">   versions, because Xcode needs the folder to symbolicate them. For devices and runtimes, give the simctl command (xcrun
</span></span><span class="line"><span class="cl">   simctl delete &lt;UDID&gt;, or xcrun simctl runtime delete &lt;identifier&gt; after
</span></span><span class="line"><span class="cl">   the same command with --dry-run). For DeviceSupport folders, name the
</span></span><span class="line"><span class="cl">   folder; there is no Apple removal command, and Xcode&#39;s behaviour when
</span></span><span class="line"><span class="cl">   that device reconnects is untested, so propose one folder first. Warn me
</span></span><span class="line"><span class="cl">   that deleting a device removes its apps and data, and that runtime
</span></span><span class="line"><span class="cl">   deletion can shut down booted simulators.
</span></span><span class="line"><span class="cl">6. After a runtime deletion, expect the space to come back late or not
</span></span><span class="line"><span class="cl">   until a restart: reread Container Free Space more than once, and check
</span></span><span class="line"><span class="cl">   whether the image is still mounted (mount | grep -i &lt;platform&gt;).
</span></span><span class="line"><span class="cl">7. Note that a du total is not what deleting frees; only the change in
</span></span><span class="line"><span class="cl">   Container Free Space is.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Then stop and wait. After I name what to remove, run only those removals,
</span></span><span class="line"><span class="cl">one at a time, with --dry-run first for runtimes, and stop if any output is
</span></span><span class="line"><span class="cl">not what you expected. Then read Container Free Space again and report the
</span></span><span class="line"><span class="cl">difference.</span></span></code></pre></div></div>
  </div>
</div><h2 id="common-failures">Common failures<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#common-failures"
     aria-label="Link to section: Common failures" data-pagefind-ignore></a>
</h2>
<h3 id="du-reports-the-coresimulatorvolumes-folder-as-larger-than-the-runtimes">du reports the <code>CoreSimulator/Volumes</code> folder as larger than the runtimes<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#du-reports-the-coresimulatorvolumes-folder-as-larger-than-the-runtimes"
     aria-label="Link to section: du reports the CoreSimulator/Volumes folder as larger than the runtimes" data-pagefind-ignore></a>
</h3>
<p><code>du</code> followed the mounts into the runtime images and counted their contents. Measure the runtime images under <code>/System/Library/AssetsV2</code> instead, as in the section on where simulator runtimes live.</p>
<h3 id="runtime-deleted-but-the-space-didnt-come-back">Runtime deleted but the space didn&rsquo;t come back<a class="heading-anchor" href="https://looselydecoupled.com/xcode-storage-simulators-device-support/#runtime-deleted-but-the-space-didnt-come-back"
     aria-label="Link to section: Runtime deleted but the space didn’t come back" data-pagefind-ignore></a>
</h3>
<p><code>xcrun simctl runtime list</code> no longer shows the runtime, yet Container Free Space hasn&rsquo;t risen and the folder under <code>/System/Library/AssetsV2</code> is the same size. The image can stay mounted while a <code>SimLaunchHost</code> process has a file from it open. Check with <code>mount | grep -i &lt;platform&gt;</code> and <code>lsof +D &lt;mount path&gt;</code>. In my run, shutting down all simulators and stopping that process still didn&rsquo;t free it, and I didn&rsquo;t test a restart. See the section on why a deleted runtime can keep its disk space.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
