App hangs in dyld at launch: injected `PackageFrameworks` rpath shadows the app bundle's embedded framework
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 3.2k
- Forks
- 106
- Avg merge
- 6d 13h
- Merged PRs (30d)
- 1
Description
Environment: Skip 1.9.7, Xcode 26.3, Swift 6.3.0 (swift-6.3-RELEASE), macOS 26.5.2 (arm64), iOS 26.3 simulator. Skip Fuse.
Summary
A stock skip create app (Skip Fuse) installs and launches on the iOS simulator but hangs
on a blank launch screen and never reaches main. No crash, no logs, no error.
The cause is dynamic-library resolution order. The linked app binary carries an rpath
pointing into the build tree:
$ otool -l CarpoolApp.app/CarpoolApp | grep -A2 LC_RPATH
path /usr/lib/swift
path <PROJECT>/.build/Darwin/DerivedData/Build/Products/Debug-iphonesimulator/PackageFrameworks
path @executable_path/Frameworks
The PackageFrameworks entry is ordered ahead of @executable_path/Frameworks, so dyld
loads CarpoolApp.framework from the build tree rather than from the app's own bundle — even
though the framework is correctly embedded in the bundle by the Embed Frameworks phase.
DYLD_PRINT_SEARCHING=1 confirms it:
find path "@rpath/CarpoolApp.framework/CarpoolApp"
...
found: dylib-from-disk: "<PROJECT>/.build/.../PackageFrameworks/CarpoolApp.framework/CarpoolApp"
...and then output stops. A sample of the hung process shows 100% of samples parked in
dyld4::JustInTimeLoader::loadDependents → SyscallDelegate::mapFileReadOnly →
dyld3::open → open_with_subsystem. The process is blocked in a file open, not spinning.
Why this matters even though it usually "works"
Loading the framework from the build tree normally succeeds, so this is invisible most of the
time. But it makes a launched app depend on its build directory remaining readable at runtime,
which is not a property an installed app should have. When that open blocks for any reason, the
app hangs before main with no diagnostic whatsoever — the hardest possible failure to debug.
In our case the trigger is location: the project lives under ~/Documents, a macOS
privacy-protected directory. We confirmed this by experiment:
| Project location | Injected rpath present | Result |
|---|---|---|
~/Documents/Development/... |
yes | hangs at blank launch screen |
~/Development/... (same scaffold, unmodified) |
yes | launches normally |
~/Documents/Development/..., rpath stripped post-link |
no | launches normally |
We did not prove the macOS mechanism behind the blocking open (TCC is the strong suspect;
tccd logged nothing and TCC.db was unreadable). Ruled out: iCloud Drive sync (not enabled),
endpoint-security extensions (none installed), code-signature differences between the two
framework copies (both ad-hoc, identical), architecture mismatch, and stale DerivedData.
Reproduction
skip createa Fuse app in a project directory under~/Documents.skip app launch.- iOS installs and launches to a blank white screen and stays there. Android is unaffected.
otool -lthe app binary and observe thePackageFrameworksrpath ahead of
@executable_path/Frameworks.
Where the rpath comes from
Not from the Skip scaffold's own settings. Darwin/CarpoolApp.xcconfig and the pbxproj set
only:
LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks
and xcodebuild -showBuildSettings confirms that is the effective value. Xcode's SwiftPM
integration injects -rpath $BUILT_PRODUCTS_DIR/PackageFrameworks at link time for the
type: .dynamic library product declared in Package.swift, outside of any visible build
setting.
So this may be Xcode behaviour rather than a Skip bug per se — but the scaffold's
.library(name:, type: .dynamic, ...) product is what triggers it, and the resulting failure
mode lands squarely on Skip users. Worth either documenting or defending against in the
scaffold.
Workaround we're using
A post-link build phase in the app target, after Embed Frameworks and before Xcode's CodeSign
step:
RPATH="${BUILT_PRODUCTS_DIR}/PackageFrameworks"
APP="${TARGET_BUILD_DIR}/${EXECUTABLE_FOLDER_PATH}"
for BIN in "${TARGET_BUILD_DIR}/${EXECUTABLE_PATH}" "${APP}/${PRODUCT_NAME}.debug.dylib"; do
[ -f "${BIN}" ] || continue
if otool -l "${BIN}" | grep -q "path ${RPATH} (offset"; then
install_name_tool -delete_rpath "${RPATH}" "${BIN}"
fi
done
With the rpath removed the app resolves only its embedded framework and launches correctly,
with the build tree left entirely in place.
Question for maintainers
Is the PackageFrameworks rpath needed at runtime for Skip Fuse apps, or is embedding the
frameworks sufficient? If the latter, stripping it in the scaffold would make Skip apps
independent of their build directory and remove a very hard-to-diagnose class of launch hang.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with Package.swift, Darwin/CarpoolApp.xcconfig, and the generated pbxproj, then reproduce with skip create and inspect the app binary using otool -l. Trace how the dynamic product creates the PackageFrameworks rpath and check whether the embedded framework is sufficient at runtime. Done means the behavior is resolved or documented with a verified launch test for projects under ~/Documents.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- build-system, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100