From 26340b2db42a2e8689c7aed34962fb68b927bf99 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 16:17:04 +0000 Subject: [PATCH] fix(ci): drop dangling symlinks before signing the macOS bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS release builds fail at the signing step, with or without signing secrets configured: xattr: No such file: .../Contents/Frameworks/PySide6/glue xattr: No such file: .../Contents/Frameworks/PySide6/include ... Error: Process completed with exit code 1 The prune step deletes unused PySide6 payload (typesystems/, include/, glue/, support/, scripts/) from Contents/Resources, but PyInstaller also cross-links each of those entries from Contents/Frameworks. Pruning one side leaves the other pointing at nothing, and `xattr -cr` stats through a symlink — it reports "No such file" and exits non-zero on a broken one, which `set -e` turns into a failed job. Sweeping the broken links is the fix rather than tolerating xattr's exit code: codesign cannot seal a link to a missing target either, and Apple's notary service rejects them. They resolve to nothing, so removing them costs nothing — and it happens on the ad-hoc path too, so the bundles built without secrets stop shipping five links to nowhere. Reproduced against a bundle shaped like the real post-prune layout with stubbed macOS tooling: the pre-fix script fails with the same five paths as CI, the fixed one drops exactly the broken links, keeps the valid Qt one, signs, and is a no-op on re-run. Verified on both the Developer ID and ad-hoc paths. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JDuaD9Tgz6PT1yUypGzAmU --- .github/signing/macos-sign.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/signing/macos-sign.sh b/.github/signing/macos-sign.sh index f137577..fd33204 100755 --- a/.github/signing/macos-sign.sh +++ b/.github/signing/macos-sign.sh @@ -58,6 +58,26 @@ else SIGN_ARGS=(--force --sign -) fi +# The build prunes unused PySide6 payload (typesystems/, include/, glue/, +# support/, scripts/) out of Contents/Resources, but PyInstaller also +# cross-links those same entries from Contents/Frameworks — so pruning +# leaves symlinks pointing at nothing. They have to go before anything +# walks the bundle: `xattr -cr` stats through a symlink and exits non-zero +# on a broken one ("No such file"), codesign cannot seal a link to a +# missing target, and Apple's notary service rejects them. Nothing is lost +# by deleting them; they already resolve to nothing. +dangling=0 +while IFS= read -r -d '' link; do + if [ ! -e "$link" ]; then + echo " dropping dangling symlink: ${link#"$APP"/} -> $(readlink "$link")" + rm -f "$link" + dangling=$((dangling + 1)) + fi +done < <(find "$APP" -type l -print0) +if [ "$dangling" -gt 0 ]; then + echo "Removed $dangling dangling symlink(s) left by the prune step." +fi + # Finder metadata and resource forks make codesign fail with "resource fork, # Finder information, or similar detritus not allowed". xattr -cr "$APP"