Skip to content

Fix nightly codesign: sign Sparkle nested executables and dock tile plugin - #2677

Merged
austinywang merged 1 commit into
mainfrom
fix-nightly-codesign-dock-tile-plugin
Apr 7, 2026
Merged

austinywang merged 1 commit into
mainfrom
fix-nightly-codesign-dock-tile-plugin

Conversation

@austinywang

@austinywang austinywang commented Apr 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fix codesign failure in nightly/release builds caused by unsigned nested bundles
  • Three-pass deepest-first signing: nested .app/.xpc bundles, standalone Mach-O executables (Sparkle's Autoupdate), then .framework/.plugin/.appex bundles
  • Applied to both nightly.yml and release.yml

Root cause

The failing nightly run failed because:

  1. CmuxDockTilePlugin.plugin was not in the manual sign loop
  2. Sparkle.framework's nested Updater.app and Autoupdate executable were not individually signed with Developer ID + timestamp

Signing a .framework bundle only signs its main binary — nested executables and app bundles inside it must be signed individually.

Fix

Before signing the outer .app, the workflow now runs three passes:

  1. Sign nested .app/.xpc bundles inside frameworks (e.g. Sparkle's Updater.app)
  2. Sign standalone Mach-O executables not inside .app bundles (e.g. Sparkle's Autoupdate)
  3. Sign .framework/.plugin/.appex bundles themselves

Files changed

  • .github/workflows/nightly.yml
  • .github/workflows/release.yml

🤖 Generated with Claude Code


Note

Medium Risk
Changes release/nightly CI signing order and file-selection logic, which can break notarization if the find/sign patterns miss or incorrectly sign embedded binaries.

Overview
Updates the macOS nightly.yml and release.yml codesigning steps to sign nested components explicitly before signing the outer .app.

Replaces the single “sign nested bundles” sweep with a three-pass deepest-first process: (1) nested .app/.xpc bundles, (2) standalone embedded Mach-O executables and .dylibs (excluding those inside .app bundles), then (3) .framework/.plugin/.appex bundles, improving codesign --verify and notarization reliability (e.g., Sparkle nested helpers / dock tile plugin).

Reviewed by Cursor Bugbot for commit 18d9fca. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes codesign in nightly and release by deep-signing nested apps and executables before the outer app. Adds a deepest-first three-pass sweep to stop verify failures and allow notarized builds.

  • Bug Fixes
    • Sign CmuxDockTilePlugin.plugin in Contents/PlugIns.
    • Individually sign Sparkle’s Updater.app and Autoupdate before Sparkle.framework.
    • Three-pass inside Contents/Frameworks and Contents/PlugIns: 1) .app/.xpc, 2) standalone Mach‑O execs and .dylib (skip inside .app), 3) .framework/.plugin/.appex.
    • Applied to .github/workflows/nightly.yml and .github/workflows/release.yml.

Written for commit 18d9fca. Summary will update on new commits.

Summary by CodeRabbit

  • Chores
    • Updated macOS code signing process in automated build workflows to implement a more structured three-pass signing approach for nested application bundles and executables, enhancing code signing reliability across both nightly and release builds.

@vercel

vercel Bot commented Apr 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 7, 2026 9:35am

@cubic-dev-ai

cubic-dev-ai Bot commented Apr 7, 2026

Copy link
Copy Markdown

This review could not be run because your cubic account has exceeded the monthly review limit. If you need help restoring access, please contact contact@cubic.dev.

@coderabbitai

coderabbitai Bot commented Apr 7, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e4772b5-3a82-485d-aa38-332485ca7848

📥 Commits

Reviewing files that changed from the base of the PR and between 9412c79 and 18d9fca.

📒 Files selected for processing (2)
  • .github/workflows/nightly.yml
  • .github/workflows/release.yml

📝 Walkthrough

Walkthrough

The pull request restructures the macOS codesigning process in two GitHub Actions workflows by replacing a single nested bundle signing pass with three explicit deepest-first passes: one for nested .app and .xpc bundles, another for Mach-O executables and .dylib files in specific directories, and a final pass for top-level nested frameworks, plugins, and app extensions.

Changes

Cohort / File(s) Summary
macOS Codesigning Restructuring
.github/workflows/nightly.yml, .github/workflows/release.yml
Replaced single deepest-first signing pass with three explicit passes: (1) nested .app and .xpc bundles, (2) Mach-O executables and .dylib files in Contents/{PlugIns,Frameworks} excluding *.app/* paths, (3) top-level nested .framework, .plugin, .appex bundles. Outer app signing unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PR #1067: Modifies macOS codesigning in nightly.yml to add per-variant signing and notarization for arm64/universal builds alongside the restructured signing passes.
  • PR #2660: Updates codesigning steps in both nightly.yml and release.yml to incorporate entitlements splitting and entitlement validation checks within the signing workflow.

Poem

🐰 Three passes deep, the code must sign,
First apps and bundles, all in line,
Then dylibs dance with Mach-O cheer,
Frameworks follow, signing near,
One hop, two hops, the outer app—
macOS blessed with codesigning's lap! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-nightly-codesign-dock-tile-plugin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The single-pass approach only signed bundle directories (.framework,
.plugin) but not the standalone executables and nested apps inside them.
Sparkle.framework contains Updater.app and Autoupdate which need to be
individually signed before the framework itself.

Three-pass signing order:
  1) Nested .app/.xpc bundles inside frameworks (deepest-first)
  2) Standalone Mach-O executables & dylibs (e.g. Sparkle's Autoupdate)
  3) .framework/.plugin/.appex bundles (deepest-first)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@austinywang
austinywang force-pushed the fix-nightly-codesign-dock-tile-plugin branch from c440b0f to 18d9fca Compare April 7, 2026 09:34
@austinywang
austinywang merged commit e88ff43 into main Apr 7, 2026
14 of 15 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is kicking off a free cloud agent to fix this issue. This run is complimentary, but you can enable autofix for all future PRs in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 18d9fca. Configure here.

if file "$f" | grep -qE 'Mach-O'; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f"
fi
done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pass 2 invalidates .xpc bundle signatures from Pass 1

High Severity

Pass 2's find excludes only *.app/* paths but not *.xpc/*. Sparkle 2.x bundles XPC services (e.g., Installer.xpc, Downloader.xpc) inside the framework. Pass 1 signs these .xpc bundles, then Pass 2 discovers and re-signs their internal main executables (which are Mach-O files with +x), invalidating the .xpc bundle signatures. Since Pass 3 only re-signs .framework/.plugin/.appex — not .xpc — the corrupted .xpc signatures persist, causing codesign --verify --deep --strict to fail.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 18d9fca. Configure here.

@greptile-apps

greptile-apps Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a three-pass deepest-first signing loop to fix the unsigned nested bundles (CmuxDockTilePlugin.plugin, Sparkle's Updater.app, Autoupdate) that caused the failing nightly run. The approach is sound, but two defects in the new signing logic will cause the build to fail at the same or the next step:

  • Pass 2's find filter excludes *.app/* but not *.xpc/*, so it re-signs executables inside Sparkle's already-sealed XPC service bundles, corrupting their CodeResources hashes. The codesign --verify --deep --strict step that immediately follows will catch this.
  • All three passes apply cmux.embedded.entitlements (which includes com.apple.security.device.camera and com.apple.security.device.audio-input) to Sparkle's third-party helpers. Apple's notarization rejects binaries that claim those entitlements without matching usage-description strings in their own Info.plist."

Confidence Score: 2/5

Not safe to merge — two independent defects in the new signing logic will cause the CI build to fail at either deep verification or Apple notarization.

Score of 2 reflects two P1 issues: the missing -not -path '.xpc/' guard invalidates XPC service bundle signatures and the deep-verify step will surface this immediately; separately, applying camera/microphone entitlements to Sparkle's helper components will cause Apple notarization to reject those binaries. Either issue alone breaks the build.

Both .github/workflows/nightly.yml (lines 421-426 and 416-431) and .github/workflows/release.yml (lines 282-287 and 278-292) require the same two fixes before this can land.

Important Files Changed

Filename Overview
.github/workflows/nightly.yml Three-pass nested signing added; Pass 2 lacks .xpc/* exclusion (breaks XPC bundle signatures) and all passes apply app entitlements with camera/mic to third-party Sparkle components (causes notarization rejection)
.github/workflows/release.yml Identical three-pass signing logic copied from nightly — same .xpc/* exclusion gap and embedded-entitlements overload for Sparkle helpers at lines 282–292

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build .app bundle] --> B[Sign CLI & Ghostty helpers\nwith EMBEDDED_ENTITLEMENTS]
    B --> C[For each: PlugIns + Frameworks dirs]
    C --> P1[Pass 1: sign .app and .xpc bundles\ndepth-first — seals XPC CodeResources]
    P1 --> P2[Pass 2: sign Mach-O executables + dylibs\n-not -path '.app/*' only]
    P2 --> BUG1{Executable inside .xpc bundle?}
    BUG1 -- Yes, not excluded --> BREAK[Re-signs inner binary\nbreaks outer .xpc seal]
    BUG1 -- No --> P3
    BREAK --> P3[Pass 3: sign .framework/.plugin/.appex]
    P3 --> OUTER[Sign outer .app]
    OUTER --> VERIFY{codesign --verify --deep --strict}
    VERIFY -- broken .xpc seal --> FAIL1[CI FAILS]
    VERIFY -- OK --> NOTARIZE[Submit to Apple notarytool]
    NOTARIZE --> BUG2{Camera/mic entitlements\non Sparkle helpers?}
    BUG2 -- Yes via EMBEDDED_ENTITLEMENTS --> FAIL2[Notarization REJECTED\nno usage descriptions]
    BUG2 -- No --> SUCCESS[Signed + notarized app]
Loading

Reviews (1): Last reviewed commit: "Fix codesign: three-pass signing for Spa..." | Re-trigger Greptile

Comment on lines +421 to +426
# Pass 2: standalone Mach-O executables & dylibs (skip those inside .app bundles)
while IFS= read -r -d '' f; do
if file "$f" | grep -qE 'Mach-O'; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f"
fi
done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0)

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.

P1 Pass 2 breaks XPC bundle signatures created in Pass 1

Pass 1 (line 419) signs .xpc bundles deepest-first — including Sparkle 2.x's InstallerLauncher.xpc and InstallerStatus.xpc. Pass 2 then re-signs individual executables inside those already-sealed bundles because the filter excludes only *.app/*, not *.xpc/*. Re-signing a file inside a signed bundle changes its content, so the outer bundle's _CodeSignature/CodeResources hash no longer matches; the XPC bundle is left with a broken signature. Pass 3 never re-signs .xpc bundles, so codesign --verify --deep --strict (line 435) will fail. The identical gap exists in release.yml line 287.

Suggested change
# Pass 2: standalone Mach-O executables & dylibs (skip those inside .app bundles)
while IFS= read -r -d '' f; do
if file "$f" | grep -qE 'Mach-O'; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f"
fi
done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0)
done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -not -path '*.xpc/*' -print0)

Comment on lines +416 to +431
# Pass 1: nested .app and .xpc bundles (deepest-first)
while IFS= read -r -d '' bundle; do
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$bundle"
done < <(find "$DIR" -depth -type d \( -name '*.plugin' -o -name '*.appex' -o -name '*.framework' -o -name '*.xpc' \) -print0)
done < <(find "$DIR" -depth -type d \( -name '*.app' -o -name '*.xpc' \) -print0)

# Pass 2: standalone Mach-O executables & dylibs (skip those inside .app bundles)
while IFS= read -r -d '' f; do
if file "$f" | grep -qE 'Mach-O'; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f"
fi
done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0)

# Pass 3: .framework, .plugin, .appex bundles (deepest-first)
while IFS= read -r -d '' bundle; do
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$bundle"
done < <(find "$DIR" -depth -type d \( -name '*.framework' -o -name '*.plugin' -o -name '*.appex' \) -print0)

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.

P1 App-level entitlements with camera/microphone applied to Sparkle's helper components

cmux.embedded.entitlements includes com.apple.security.device.camera and com.apple.security.device.audio-input. All three passes stamp these entitlements onto every nested bundle and executable — including Sparkle's Autoupdate, Updater.app, and its XPC services. Apple's notarization service rejects any hardened-runtime binary that claims camera or microphone access without a corresponding NSCameraUsageDescription / NSMicrophoneUsageDescription in that binary's own Info.plist. Sparkle's helpers carry neither, so notarization will fail on those binaries. The same issue exists in release.yml lines 278–292.

Sparkle's internals should be signed with no custom entitlements file (or a minimal one containing only the cs.* exceptions actually needed by Ghostty). Reserve --entitlements "$EMBEDDED_ENTITLEMENTS" for the app's own CLI and helper binaries, not for third-party framework components.

This branch was successfully deployed

1 active deployment
Preview — 18d9fca6 Deployed Apr 7, 2026 by vercel[bot]
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.

1 participant