Skip to content

fix(build): handle non-standard Python.framework signing in Tauri build#1249

Merged
ErikBjare merged 3 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/tauri-python-framework-signing
Apr 8, 2026
Merged

fix(build): handle non-standard Python.framework signing in Tauri build#1249
ErikBjare merged 3 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/tauri-python-framework-signing

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

Problem

Post-merge master Build Tauri is failing on both macOS runners with:

dist/ActivityWatch.app/Contents/Resources/aw-watcher-window/Python.framework: bundle format is ambiguous (could be app or framework)
make: *** [dist/ActivityWatch.app] Error 1

This is happening in Step 2 of the inside-out codesign loop (introduced in #1246, patched in #1247), which tries to sign all .framework directories as bundles. PyInstaller-embedded Python.framework bundles (inside aw-watcher-window and aw-watcher-input) lack the standard Versions/ directory structure and Info.plist that codesign requires to sign a proper framework bundle, so it rejects them with "bundle format is ambiguous".

#1247 correctly fixed Step 1 (skipping the Python.framework/Python binary as a standalone file). But Step 2 still unconditionally called sign_binary on the .framework directory itself, hitting the same ambiguous-bundle 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 so real signing problems are still surfaced.

This completes the signing chain:

Related

…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.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

This PR fixes a post-merge CI failure in the Tauri macOS build by adding a fallback in the Step 2 codesign loop: when codesign rejects a .framework directory with "bundle format is ambiguous" (because PyInstaller-embedded frameworks lack the standard Versions/ structure and Info.plist), it now signs the main binary inside the framework directly instead of failing fatally. All other codesign errors remain fatal, preserving real-error detection.

Confidence Score: 5/5

Safe to merge — the fix is targeted, non-standard framework signing errors are caught precisely, and all other codesign failures remain fatal.

The only finding is a P2 observability suggestion (silent skip warning instead of hard exit when the fallback binary is absent). This does not affect correctness for the known PyInstaller layout, and the existing if [ -f ] guard is intentionally lenient. No P0/P1 issues found.

No files require special attention.

Vulnerabilities

No security concerns identified. The change narrows the error-handling surface rather than expanding it — only the specific "bundle format is ambiguous" message triggers the fallback path, and all other codesign failures still cause a hard exit.

Important Files Changed

Filename Overview
scripts/package/build_app_tauri.sh Adds fallback in Step 2 codesign loop: catches "bundle format is ambiguous" for non-standard PyInstaller-embedded frameworks and signs the main binary directly; all other codesign errors remain fatal.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Step 1: Sign all Mach-O files\nlength-sorted deepest-first] --> B{Parent dir is\n.framework/.bundle/.plugin?}
    B -- Yes --> C[Skip — will be handled\nin Step 2]
    B -- No --> D[sign_binary file]
    C --> E
    D --> E[Step 2: Sign bundle directories\n.framework / .bundle / .plugin\nlength-sorted deepest-first]
    E --> F{codesign succeeds?}
    F -- Yes --> G[Signed bundle ✓]
    F -- No --> H{Error contains\n'bundle format\nis ambiguous'?}
    H -- Yes --> I[Fallback: sign main binary\ne.g. Python.framework/Python]
    I --> J{Binary exists\nat fw/fw_name?}
    J -- Yes --> K[sign_binary fw_binary ✓]
    J -- No --> L[Warning: skip —\nbinary not found]
    H -- No --> M[echo ERROR + exit 1 ✗]
    G --> N[Step 3: Sign top-level\n.app bundle last]
    K --> N
    L --> N
Loading

Reviews (1): Last reviewed commit: "fix(build): handle non-standard Python.f..." | Re-trigger Greptile

Comment thread scripts/package/build_app_tauri.sh Outdated
Comment on lines +162 to +163
echo " Warning: No main binary found at $fw_binary, skipping"
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent skip may hide unsigned components

If the main binary isn't found at $fw_binary, the framework is left unsigned with just a warning. Because notarytool's rejection message for unsigned embedded content doesn't always name the missing binary, a silent skip here could produce a confusing downstream notarization failure rather than a clear build error.

Consider making this fatal, or at least surfacing it more loudly (e.g., exit 1), since a missing main binary at the expected path likely indicates a structural change in PyInstaller's output that needs attention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c2c0af4: changed the silent-skip to a fatal error (exit 1 with a clear message). If $fw_binary is not found, it now prints ERROR: Expected main binary not found at $fw_binary + a hint about PyInstaller output changes, then exits. A missing binary at this point means PyInstaller changed its structure, which warrants a loud build failure rather than a silent skip that could cause confusing notarization errors later.

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.
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.
@TimeToBuildBob TimeToBuildBob force-pushed the fix/tauri-python-framework-signing branch from 7b1d644 to c2c0af4 Compare April 8, 2026 21:03
@ErikBjare ErikBjare merged commit 6d836c0 into ActivityWatch:master Apr 8, 2026
17 checks passed
TimeToBuildBob added a commit to TimeToBuildBob/activitywatch that referenced this pull request Apr 8, 2026
… ambiguity

codesign refuses to sign Python.framework/Python in-place when the binary
is inside a .framework directory — it sees the directory context and reports
'bundle format is ambiguous (could be app or framework)'.

The ActivityWatch#1249 fallback correctly detected this case but then called sign_binary
on the same path, which hits the same codesign check.

Fix: copy the binary to a temp path outside any .framework dir, sign it
there, then copy the signed binary back. Code signatures are embedded in
the Mach-O binary (not path-dependent), so the result is identical.

This should be the final fix needed to unblock the Build Tauri master CI
and allow the Thursday 2026-04-09 12:00 UTC scheduled dev release to run.
ErikBjare pushed a commit that referenced this pull request Apr 9, 2026
… ambiguity (#1250)

codesign refuses to sign Python.framework/Python in-place when the binary
is inside a .framework directory — it sees the directory context and reports
'bundle format is ambiguous (could be app or framework)'.

The #1249 fallback correctly detected this case but then called sign_binary
on the same path, which hits the same codesign check.

Fix: copy the binary to a temp path outside any .framework dir, sign it
there, then copy the signed binary back. Code signatures are embedded in
the Mach-O binary (not path-dependent), so the result is identical.

This should be the final fix needed to unblock the Build Tauri master CI
and allow the Thursday 2026-04-09 12:00 UTC scheduled dev release to run.
TimeToBuildBob added a commit to TimeToBuildBob/activitywatch that referenced this pull request Apr 9, 2026
…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.
TimeToBuildBob added a commit to TimeToBuildBob/activitywatch that referenced this pull request Apr 9, 2026
… ambiguity (ActivityWatch#1250)

codesign refuses to sign Python.framework/Python in-place when the binary
is inside a .framework directory — it sees the directory context and reports
'bundle format is ambiguous (could be app or framework)'.

The ActivityWatch#1249 fallback correctly detected this case but then called sign_binary
on the same path, which hits the same codesign check.

Fix: copy the binary to a temp path outside any .framework dir, sign it
there, then copy the signed binary back. Code signatures are embedded in
the Mach-O binary (not path-dependent), so the result is identical.

This should be the final fix needed to unblock the Build Tauri master CI
and allow the Thursday 2026-04-09 12:00 UTC scheduled dev release to run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants