<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ritesh Sethia</title>
    <description>The latest articles on DEV Community by Ritesh Sethia (@riteshsethia).</description>
    <link>https://dev.to/riteshsethia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4239781%2F23fa8667-c79d-4a95-a9d2-e547551b565a.jpg</url>
      <title>DEV Community: Ritesh Sethia</title>
      <link>https://dev.to/riteshsethia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/riteshsethia"/>
    <language>en</language>
    <item>
      <title>SwiftPM's Bundle.module crashes your Mac app on every machine but yours</title>
      <dc:creator>Ritesh Sethia</dc:creator>
      <pubDate>Wed, 11 Nov 2026 20:02:23 +0000</pubDate>
      <link>https://dev.to/riteshsethia/swiftpms-bundlemodule-crashes-your-mac-app-on-every-machine-but-yours-4ji6</link>
      <guid>https://dev.to/riteshsethia/swiftpms-bundlemodule-crashes-your-mac-app-on-every-machine-but-yours-4ji6</guid>
      <description>&lt;p&gt;I shipped a Mac app. It was signed, notarized, stapled, and verified by&lt;br&gt;
Gatekeeper. 378 tests passed. I installed it from the DMG on my own machine and&lt;br&gt;
used it for an hour.&lt;/p&gt;

&lt;p&gt;A friend downloaded it. It crashed on launch. Not misbehaved — crashed,&lt;br&gt;
instantly, every time, before a window appeared.&lt;/p&gt;

&lt;p&gt;The cause is a generated accessor that most people never read, and it will do&lt;br&gt;
the same thing to your app if you build a &lt;code&gt;.app&lt;/code&gt; out of Swift Package Manager&lt;br&gt;
targets that have resources.&lt;/p&gt;
&lt;h2&gt;
  
  
  What SwiftPM generates
&lt;/h2&gt;

&lt;p&gt;When a target declares &lt;code&gt;resources:&lt;/code&gt;, SwiftPM writes a file called&lt;br&gt;
&lt;code&gt;resource_bundle_accessor.swift&lt;/code&gt; into the build directory and compiles it into&lt;br&gt;
your module. That file is what &lt;code&gt;Bundle.module&lt;/code&gt; resolves to. It looks roughly&lt;br&gt;
like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;Foundation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bundle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bundleName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Arazio_ArazioCore"&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="cm"&gt;/* SWIFT_PACKAGE_RESOURCE_BUNDLE_PATH, if set */&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;overrides&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BundleFinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundleURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendingPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundleName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".bundle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"unable to find bundle named Arazio_ArazioCore"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact candidate list has changed across toolchain versions, which is part&lt;br&gt;
of why this is hard to pin down. The shape that matters has not changed: &lt;strong&gt;a&lt;br&gt;
short list of guesses, and then &lt;code&gt;fatalError&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And on some versions, one of those guesses — or the override baked in at&lt;br&gt;
compile time — is an absolute path into the &lt;code&gt;.build&lt;/code&gt; directory of the machine&lt;br&gt;
that compiled the binary.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why it works for you and nobody else
&lt;/h2&gt;

&lt;p&gt;Run the app from Xcode or from &lt;code&gt;swift run&lt;/code&gt;, and the bundle is sitting right&lt;br&gt;
next to the executable in &lt;code&gt;.build/release/&lt;/code&gt;. Found on the first or second&lt;br&gt;
candidate. Everything works.&lt;/p&gt;

&lt;p&gt;Now assemble a real &lt;code&gt;.app&lt;/code&gt;. The convention — and what &lt;code&gt;codesign&lt;/code&gt; expects, and&lt;br&gt;
what every bundling script does — is that resources go in&lt;br&gt;
&lt;code&gt;Arazio.app/Contents/Resources/&lt;/code&gt;. But &lt;code&gt;Bundle.main.bundleURL&lt;/code&gt; is&lt;br&gt;
&lt;code&gt;Arazio.app/&lt;/code&gt;, not &lt;code&gt;Contents/Resources/&lt;/code&gt;. So that candidate misses.&lt;/p&gt;

&lt;p&gt;On your machine, the fallback into &lt;code&gt;.build&lt;/code&gt; still resolves, because that&lt;br&gt;
directory is right there on your disk. The app finds its resources through a&lt;br&gt;
path that has nothing to do with the bundle it is supposedly running from. Your&lt;br&gt;
own development tree is silently propping up the shipped application.&lt;/p&gt;

&lt;p&gt;On a customer's machine that path does not exist. Neither does any other&lt;br&gt;
candidate. So: &lt;code&gt;fatalError&lt;/code&gt;, on the first line of&lt;br&gt;
&lt;code&gt;applicationDidFinishLaunching&lt;/code&gt;, with a crash report naming a directory on a&lt;br&gt;
stranger's disk.&lt;/p&gt;
&lt;h2&gt;
  
  
  Nothing in a normal pipeline catches this
&lt;/h2&gt;

&lt;p&gt;This is the part worth sitting with. Every check I had was green:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;swift build -c release&lt;/code&gt; — succeeded.&lt;/li&gt;
&lt;li&gt;The full test suite — 378 tests, passing. They run on my machine, where the
path exists.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;codesign --verify --deep --strict&lt;/code&gt; — valid.&lt;/li&gt;
&lt;li&gt;Notarization — accepted by Apple.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stapler validate&lt;/code&gt; and &lt;code&gt;spctl --assess&lt;/code&gt; — accepted.&lt;/li&gt;
&lt;li&gt;Installing from the DMG and using the app — worked perfectly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not one of those can see the problem, because every one of them runs on the&lt;br&gt;
machine that has the &lt;code&gt;.build&lt;/code&gt; directory. Apple's notary service checks that&lt;br&gt;
your code is signed and free of known malware. It does not launch your app on a&lt;br&gt;
clean machine and see whether it survives.&lt;/p&gt;

&lt;p&gt;The only thing that catches this is a machine that is not yours.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Stop using &lt;code&gt;Bundle.module&lt;/code&gt; for anything that ships inside a &lt;code&gt;.app&lt;/code&gt;. Write a&lt;br&gt;
lookup that checks where resources actually live, in order, and returns &lt;code&gt;nil&lt;/code&gt;&lt;br&gt;
instead of trapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;ResourceBundle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;/// Anchors `Bundle(for:)` to this module, which is how the bundle is found&lt;/span&gt;
    &lt;span class="c1"&gt;/// under a test runner: there `Bundle.main` is the xctest tool, and none of&lt;/span&gt;
    &lt;span class="c1"&gt;/// the app-shaped paths exist.&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Anchor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".bundle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".bundle"&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;// Where an .app keeps them, and where the bundling script puts them.&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceURL&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendingPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Where SwiftPM's own accessor looks, in case that becomes right.&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundleURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendingPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Beside the executable, where a plain `swift build` leaves them.&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executableURL&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deletingLastPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendingPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Under a test runner, or anything else that is not our .app: the&lt;/span&gt;
            &lt;span class="c1"&gt;// bundle sits beside the binary that contains this code.&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceURL&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendingPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundleURL&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deletingLastPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendingPathComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="nc"&gt;FileManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fileExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;atPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that are easy to get wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Bundle.main.resourceURL&lt;/code&gt; goes first.&lt;/strong&gt; That is &lt;code&gt;Contents/Resources&lt;/code&gt; inside a&lt;br&gt;
real &lt;code&gt;.app&lt;/code&gt;, which is the correct answer and the one SwiftPM does not try&lt;br&gt;
first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;Anchor&lt;/code&gt; class is not optional.&lt;/strong&gt; My first attempt at this fix dropped&lt;br&gt;
the &lt;code&gt;Bundle(for:)&lt;/code&gt; candidates, on the reasoning that an app-shaped lookup is&lt;br&gt;
what an app needs. That broke 273 tests instantly. Under a test runner,&lt;br&gt;
&lt;code&gt;Bundle.main&lt;/code&gt; is the &lt;code&gt;xctest&lt;/code&gt; binary, and none of the app paths exist — the&lt;br&gt;
bundle sits beside the &lt;em&gt;module's&lt;/em&gt; binary, which is what &lt;code&gt;Bundle(for:)&lt;/code&gt; finds.&lt;br&gt;
Returning &lt;code&gt;nil&lt;/code&gt; rather than trapping also means a caller can fail with a message&lt;br&gt;
a person can act on, instead of a stack trace.&lt;/p&gt;

&lt;p&gt;Note the deliberate absence of a &lt;code&gt;.build&lt;/code&gt; fallback. If the resources are not in&lt;br&gt;
one of those five real locations, the app is broken and should say so — not&lt;br&gt;
quietly borrow them from a directory that only exists here.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part that actually matters
&lt;/h2&gt;

&lt;p&gt;Fixing it is twenty minutes. Making sure it never happens again is the point,&lt;br&gt;
and it is the part I would not have bothered with before this shipped broken.&lt;/p&gt;

&lt;p&gt;A release cannot depend on me remembering to test on a borrowed Mac. So the app&lt;br&gt;
grew a flag that loads everything it needs from its own bundle and exits,&lt;br&gt;
without opening a window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;CommandLine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"--verify-resources"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;ResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Arazio_ArazioCore"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Arazio_ArazioCore.bundle not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;ResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Arazio_ArazioUI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Arazio_ArazioUI.bundle not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nc"&gt;FormatRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadBundled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allFormats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;FormatRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LoadError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;manifestMissing&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"  format manifest: \(registry.allFormats.count) formats"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FormatRegistry.loadBundled failed: \(error)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// …every other resource the app needs to start&lt;/span&gt;

    &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the release script takes the prop away before running it — it moves the&lt;br&gt;
build directory's resource bundles aside, asks the staged app to load&lt;br&gt;
everything, then puts them back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"==&amp;gt; Verifying the app stands on its own"&lt;/span&gt;
&lt;span class="nv"&gt;STASH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$BUILD/resource-stash"&lt;/span&gt;
&lt;span class="nv"&gt;BUILT_PRODUCTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$(cd "&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;" &amp;amp;&amp;amp; swift build -c release --show-bin-path)"&lt;/span&gt;
rm -rf &lt;span class="s2"&gt;"$STASH"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; mkdir -p &lt;span class="s2"&gt;"$STASH"&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt; -s nullglob
&lt;span class="nv"&gt;STASHED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$BUILT_PRODUCTS"&lt;/span&gt;/*.bundle&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt; -u nullglob
&lt;span class="k"&gt;for&lt;/span&gt; BUNDLE &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"${STASHED[@]}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; mv &lt;span class="s2"&gt;"$BUNDLE"&lt;/span&gt; &lt;span class="s2"&gt;"$STASH/"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;

restore_bundles&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;shopt&lt;/span&gt; -s nullglob
  &lt;span class="k"&gt;for&lt;/span&gt; BUNDLE &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"$STASH"&lt;/span&gt;/*.bundle&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; mv &lt;span class="s2"&gt;"$BUNDLE"&lt;/span&gt; &lt;span class="s2"&gt;"$BUILT_PRODUCTS/"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
  &lt;span class="nb"&gt;shopt&lt;/span&gt; -u nullglob
  rmdir &lt;span class="s2"&gt;"$STASH"&lt;/span&gt; 2&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; restore_bundles EXIT

&lt;span class="k"&gt;if&lt;/span&gt; ! &lt;span class="s2"&gt;"$APP/Contents/MacOS/Arazio"&lt;/span&gt; --verify-resources&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  restore_bundles
  &lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"error: the app cannot find its own resources without this machine's"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"       build directory. It would crash on launch for every customer."&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; 1
&lt;span class="k"&gt;fi&lt;/span&gt;
restore_bundles
&lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;trap&lt;/code&gt; matters more than it looks. If the check fails, or the script is&lt;br&gt;
interrupted between the two &lt;code&gt;mv&lt;/code&gt; loops, the bundles have to go back — otherwise&lt;br&gt;
a failed release leaves your working tree in a state where the &lt;em&gt;next&lt;/em&gt; build is&lt;br&gt;
also broken, for an entirely different reason.&lt;/p&gt;

&lt;p&gt;This is, as far as I can tell, the only way to see what a stranger's Mac sees&lt;br&gt;
without owning one.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you take one thing
&lt;/h2&gt;

&lt;p&gt;The bug was never really &lt;code&gt;Bundle.module&lt;/code&gt;. The bug was that &lt;strong&gt;every check I had&lt;br&gt;
ran on the machine that had the thing being tested for.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is worth turning into a standing question rather than a one-off fix. What&lt;br&gt;
else is true on my machine that is not true on a customer's? A Homebrew library&lt;br&gt;
on the link path. A font. A permission granted months ago and forgotten. A&lt;br&gt;
filesystem — a later release of the same app refused every conversion on exFAT&lt;br&gt;
drives, because &lt;code&gt;volumeAvailableCapacityForImportantUsage&lt;/code&gt; returns zero on&lt;br&gt;
anything that is not APFS, and I only own APFS volumes.&lt;/p&gt;

&lt;p&gt;Each of those is invisible to a test suite that runs where you build. The fix&lt;br&gt;
is not to test harder. It is to make the release take away the thing you are&lt;br&gt;
standing on, and see whether the app is still upright.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written while building &lt;a href="https://arazioformac.com" rel="noopener noreferrer"&gt;Arazio&lt;/a&gt;, a Mac app that converts files from the&lt;br&gt;
drag itself. The crash described here was version 1.0.0, fixed in 1.0.1, about&lt;br&gt;
six hours apart.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>macos</category>
      <category>swiftpm</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
