fix(build): skip framework main binaries in codesign Step 1#1247
Conversation
Greptile SummaryThis PR fixes the CI failure introduced by #1246 by adding a skip guard in the Step 1 Mach-O signing loop: files whose immediate parent directory is a Confidence Score: 5/5Safe to merge — the fix correctly resolves the CI failure without introducing new issues. The skip guard precisely targets the ambiguous-bundle-binary problem for .framework/Versions/* paths (the root cause), and the extension of Step 2 to .bundle/.plugin is a sensible hardening. All remaining open findings (entitlements on dylibs, deeper .bundle/.plugin nesting) were already raised in prior review threads and are pre-existing; none are regressions from this PR. No new P0/P1 issues were found. No files require special attention.
|
| Filename | Overview |
|---|---|
| scripts/package/build_app_tauri.sh | Adds parent-directory skip logic in Step 1 to avoid codesigning framework/bundle main binaries as standalone files; extends Step 2 to also sign .bundle and .plugin directories. Logic is correct for the Python.framework case. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[find all files in .app] --> B["batch file-type check via xargs file + grep Mach-O"]
B --> C{parent_dir matches .framework,\n.framework/Versions/*,\n.bundle, or .plugin?}
C -- Yes --> D[Skip — defer to Step 2]
C -- No --> E["sign_binary: codesign --force --options runtime\n--timestamp --entitlements ..."]
E --> F[Step 1 complete]
D --> F
F --> G["find .framework / .bundle / .plugin dirs\n(sorted deepest-first)"]
G --> H[sign_binary each bundle dir]
H --> I[Step 2 complete]
I --> J[sign_binary dist/ActivityWatch.app]
J --> K[Signing complete]
Reviews (2): Last reviewed commit: "fix(build): skip framework main binaries..." | Re-trigger Greptile
| if [[ "$parent_dir" == *.framework ]] || [[ "$parent_dir" == *.framework/Versions/* ]] \ | ||
| || [[ "$parent_dir" == *.bundle ]] || [[ "$parent_dir" == *.plugin ]]; then |
There was a problem hiding this comment.
.bundle/.plugin skip patterns don't cover nested binaries
The check [[ "$parent_dir" == *.bundle ]] only matches a file sitting directly inside Foo.bundle/ (e.g., Foo.bundle/Foo). The conventional macOS bundle layout places the main binary one level deeper at Foo.bundle/Contents/MacOS/Foo, whose parent Foo.bundle/Contents/MacOS does not match *.bundle. The same gap applies to *.plugin. This means those main binaries still get signed standalone in Step 1, then re-signed as part of their bundle in Step 2. The --force flag lets Step 2 succeed, but the intermediate Step 1 signature is wasted work. If any .bundle or .plugin encountered in this tree happens to trigger the same codesign "bundle format is ambiguous" error as Python.framework/Python, it would break the build.
Consider also matching the typical Contents/MacOS nesting:
| if [[ "$parent_dir" == *.framework ]] || [[ "$parent_dir" == *.framework/Versions/* ]] \ | |
| || [[ "$parent_dir" == *.bundle ]] || [[ "$parent_dir" == *.plugin ]]; then | |
| if [[ "$parent_dir" == *.framework ]] || [[ "$parent_dir" == *.framework/Versions/* ]] \ | |
| || [[ "$parent_dir" == *.bundle ]] || [[ "$parent_dir" == *.bundle/Contents/MacOS ]] \ | |
| || [[ "$parent_dir" == *.plugin ]] || [[ "$parent_dir" == *.plugin/Contents/MacOS ]]; then |
| sign_binary() { | ||
| echo " Signing: $1" | ||
| codesign --force --options runtime --timestamp \ | ||
| --entitlements "$ENTITLEMENTS" \ | ||
| --sign "$APPLE_PERSONALID" \ | ||
| "$1" | ||
| } |
There was a problem hiding this comment.
Entitlements applied to all Mach-O files including dylibs
sign_binary applies --entitlements "$ENTITLEMENTS" to every signed object — dylibs, .so files, frameworks, and the top-level app alike. Apple's notarization guidance states that entitlements should only be embedded in executables and app/framework bundles, not in plain shared libraries. Embedding them in dylibs is silently accepted by codesign but can cause xcnotary warnings or notarization rejections. The old --deep code had the same issue, so this isn't a regression, but the new per-file loop is a good place to fix it: pass --entitlements only for the .app and optionally .framework/.bundle/.plugin targets, and omit it for bare dylibs/.so files.
…ambiguity error The inside-out signing loop from ActivityWatch#1246 signs ALL Mach-O files in Step 1, then .framework bundles in Step 2. But Python.framework/Python is both a Mach-O binary AND the main binary of a .framework bundle — codesign errors with "bundle format is ambiguous (could be app or framework)" when it's signed as a standalone file. Fix: Skip files whose parent directory is a .framework, .bundle, or .plugin in Step 1. These are correctly signed as part of their bundle in Step 2. Also extends Step 2 to cover .bundle and .plugin directories (not just .framework) for completeness. Fixes the Build Tauri master CI failure after ActivityWatch#1246 merge.
c3a2738 to
3dfd97b
Compare
|
All macOS CI green — both Build and Build Tauri pass on macOS-14 and macOS-latest. The "bundle format is ambiguous" error is resolved. Only Windows builds still running (unrelated to codesign). |
|
@greptileai review |
|
Tip: Greploops — Automatically fix all review issues by running Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal. |
Python.framework (created by PyInstaller) has an ambiguous bundle structure that causes codesign to error with "bundle format is ambiguous (could be app or framework)" when signed as a bundle. The previous fix (ActivityWatch#1247) only skipped the framework's main binary in Step 1 (per-binary signing). But Step 2 (bundle-level signing) also encounters this error when trying to sign the framework bundle itself. Fix: make bundle signing non-fatal in Step 2. Since all individual Mach-O files inside the framework are already signed in Step 1, and the top-level .app is signed in Step 3, the framework-level seal is not strictly required.
PyInstaller-embedded Python.framework bundles (inside aw-watcher-window, aw-watcher-input) lack the standard Versions/ directory structure and Info.plist, so codesign rejects them in Step 2 with: bundle format is ambiguous (could be app or framework) Previously ActivityWatch#1247 correctly skipped the Python.framework/Python binary in Step 1 (standalone signing of the main framework binary causes ambiguous errors), but Step 2 still unconditionally attempted bundle-signing the .framework directory itself, hitting the same error. Fix: in Step 2, catch "bundle format is ambiguous" errors and fall back to signing the main binary inside the framework directly (e.g. Python.framework/Python). All other codesign errors remain fatal. This completes the signing chain: the .so files inside Python.framework are signed in Step 1, the Python binary itself is signed in this Step 2 fallback, and standard frameworks/bundles are signed normally.
PyInstaller-embedded Python.framework bundles (inside aw-watcher-window, aw-watcher-input) lack the standard Versions/ directory structure and Info.plist, so codesign rejects them in Step 2 with: bundle format is ambiguous (could be app or framework) Previously ActivityWatch#1247 correctly skipped the Python.framework/Python binary in Step 1 (standalone signing of the main framework binary causes ambiguous errors), but Step 2 still unconditionally attempted bundle-signing the .framework directory itself, hitting the same error. Fix: in Step 2, catch "bundle format is ambiguous" errors and fall back to signing the main binary inside the framework directly (e.g. Python.framework/Python). All other codesign errors remain fatal. This completes the signing chain: the .so files inside Python.framework are signed in Step 1, the Python binary itself is signed in this Step 2 fallback, and standard frameworks/bundles are signed normally.
…ld (#1249) * fix(build): skip framework main binaries in codesign Step 1 to avoid ambiguity error The inside-out signing loop from #1246 signs ALL Mach-O files in Step 1, then .framework bundles in Step 2. But Python.framework/Python is both a Mach-O binary AND the main binary of a .framework bundle — codesign errors with "bundle format is ambiguous (could be app or framework)" when it's signed as a standalone file. Fix: Skip files whose parent directory is a .framework, .bundle, or .plugin in Step 1. These are correctly signed as part of their bundle in Step 2. Also extends Step 2 to cover .bundle and .plugin directories (not just .framework) for completeness. Fixes the Build Tauri master CI failure after #1246 merge. * fix(build): handle non-standard Python.framework signing in Tauri build PyInstaller-embedded Python.framework bundles (inside aw-watcher-window, aw-watcher-input) lack the standard Versions/ directory structure and Info.plist, so codesign rejects them in Step 2 with: bundle format is ambiguous (could be app or framework) Previously #1247 correctly skipped the Python.framework/Python binary in Step 1 (standalone signing of the main framework binary causes ambiguous errors), but Step 2 still unconditionally attempted bundle-signing the .framework directory itself, hitting the same error. Fix: in Step 2, catch "bundle format is ambiguous" errors and fall back to signing the main binary inside the framework directly (e.g. Python.framework/Python). All other codesign errors remain fatal. This completes the signing chain: the .so files inside Python.framework are signed in Step 1, the Python binary itself is signed in this Step 2 fallback, and standard frameworks/bundles are signed normally. * fix(build): make missing fw_binary fatal instead of silent skip If the main binary is not found at the expected path after an ambiguous-bundle error, emit a clear error and exit 1 rather than silently skipping. A missing binary here means PyInstaller changed its output structure — a silent skip would produce a confusing downstream notarization failure instead of a clear build error. Addresses Greptile review comment (P2) on #1249.
…ld (ActivityWatch#1249) * fix(build): skip framework main binaries in codesign Step 1 to avoid ambiguity error The inside-out signing loop from ActivityWatch#1246 signs ALL Mach-O files in Step 1, then .framework bundles in Step 2. But Python.framework/Python is both a Mach-O binary AND the main binary of a .framework bundle — codesign errors with "bundle format is ambiguous (could be app or framework)" when it's signed as a standalone file. Fix: Skip files whose parent directory is a .framework, .bundle, or .plugin in Step 1. These are correctly signed as part of their bundle in Step 2. Also extends Step 2 to cover .bundle and .plugin directories (not just .framework) for completeness. Fixes the Build Tauri master CI failure after ActivityWatch#1246 merge. * fix(build): handle non-standard Python.framework signing in Tauri build PyInstaller-embedded Python.framework bundles (inside aw-watcher-window, aw-watcher-input) lack the standard Versions/ directory structure and Info.plist, so codesign rejects them in Step 2 with: bundle format is ambiguous (could be app or framework) Previously ActivityWatch#1247 correctly skipped the Python.framework/Python binary in Step 1 (standalone signing of the main framework binary causes ambiguous errors), but Step 2 still unconditionally attempted bundle-signing the .framework directory itself, hitting the same error. Fix: in Step 2, catch "bundle format is ambiguous" errors and fall back to signing the main binary inside the framework directly (e.g. Python.framework/Python). All other codesign errors remain fatal. This completes the signing chain: the .so files inside Python.framework are signed in Step 1, the Python binary itself is signed in this Step 2 fallback, and standard frameworks/bundles are signed normally. * fix(build): make missing fw_binary fatal instead of silent skip If the main binary is not found at the expected path after an ambiguous-bundle error, emit a clear error and exit 1 rather than silently skipping. A missing binary here means PyInstaller changed its output structure — a silent skip would produce a confusing downstream notarization failure instead of a clear build error. Addresses Greptile review comment (P2) on ActivityWatch#1249.
Summary
Fixes the Build Tauri master CI failure introduced by #1246.
Python.framework/Pythonis both a Mach-O file and the main binary of a.frameworkbundle — codesign errors with "bundle format is ambiguous (could be app or framework)".framework,.bundle, or.pluginin Step 1 — these are correctly signed as part of their bundle in Step 2.bundleand.plugindirectories (not just.framework)CI error from run 24137803771:
Test plan
xcnotary prechecksucceeds on the signed.app