Skip to content

Widget extension fails to launch on macOS 26.5 (Tahoe) — WidgetBundle.main() returns instead of blocking, plus team-ID parsing bug in compile_and_run.sh #1095

@jamesjlopez

Description

@jamesjlopez

Environment

  • macOS 26.5 (build 25F71) — Apple silicon (Mac mini M-series)
  • Xcode 26.5 (17F42)
  • Swift 6.3.2 (swiftlang-6.3.2.1.108)
  • Apple Development cert: Apple Development: <email> (5X9WMS8T3G) — note: certificate subject is CN=…(5X9WMS8T3G)/OU=38Z85GV2D8, i.e. the trailing (…) in CN is not the team ID.

Reproduction

git clone https://github.com/steipete/CodexBar.git
cd CodexBar
./Scripts/compile_and_run.sh
# App launches, menu bar icon appears. Try to add a CodexBar widget — none in the gallery.

Bug 1 — Team ID parsed from wrong cert field

Scripts/compile_and_run.sh:88-97 extracts the team ID from the (...) suffix of the certificate's CN. For some Apple Development certificates that value is the Apple ID identifier, not the Team ID. Result: app and widget extension get signed with TeamIdentifier=<real_team> but their entitlements claim app group <wrong_team>.com.steipete.codexbar, which macOS rejects.

Reproducer: cert subject CN=Apple Development: …(5X9WMS8T3G)/OU=38Z85GV2D8/O=…/C=US. The script picks 5X9WMS8T3G; correct team ID is 38Z85GV2D8 (in OU).

Suggested fix: Read team ID from the certificate's OU field via security find-certificate -c "$identity" -p | openssl x509 -noout -subject, fall back to the existing CN-suffix parse only if OU lookup fails.

Workaround: set APP_TEAM_ID env var before running the script.

Bug 2 — Widget extension exits immediately on macOS 26 because WidgetBundle.main() does not block

After working around Bug 1, the widget extension is correctly signed and registered with pluginkit / chronod, but every getAllDescriptors query fails with:

chronod: (WidgetKit) [com.apple.chrono:session] Unexpected error on session:
  Error Domain=NSCocoaErrorDomain Code=4099
  "The connection to service with pid -1 named (null) was invalidated."

The widget extension is launched by chronod, logs progress, and then exits cleanly with status 0 (no crash report). Sequence from log show:

CodexBarWidget: (libsystem_secinit.dylib) AppSandbox
CodexBarWidget: (WidgetKit) [com.apple.chrono:widget] main [WidgetBundle]
CodexBarWidget: (WidgetKit) [com.apple.chrono:widget] Locale token: …
CodexBarWidget: (WidgetKit) [com.apple.chrono:widget] WidgetHost - Optional(WidgetKit.ResolvedWidgetBundleHost)
CodexBarWidget: (ExtensionFoundation) [com.apple.extensionkit:launch] Extension Type: '<private>' : <private>
runningboardd: termination reported by launchd (0, 0, 0)

lldb backtrace at exit()

With get-task-allow added to the widget entitlements and lldb -o "process attach --name CodexBarWidget --waitfor" plus a breakpoint on exit:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001866c42d0 libdyld.dylib`dyld4::LibSystemHelpers::exit(int) const
    frame #1: 0x00000001866fbf0c dyld`dyld4::LibSystemHelpersWrapper::exit(int) const + 164
    frame #2: 0x00000001866fbe30 dyld`start + 7040

Three frames — exit is being called from dyld's post-main epilogue. The user's main() returned with code 0. That is: SwiftUI's @main-generated entry point calls WidgetBundle.main(), that returns, and the process exits without entering an XPC runloop. On macOS 14/15 this presumably worked because ExtensionFoundation set up a blocking runloop internally; on macOS 26 it does not.

Things ruled out

Tried Result
NSExtension+NSExtensionPrincipalClass (current default) Crashes _EXRunningExtension with Fatal error: Unrecognized extension type
NSExtension (no PrincipalClass) Same Unrecognized extension type crash
EXAppExtensionAttributes { EXExtensionPointIdentifier } Past the crash, but now exits cleanly without serving descriptors
Both NSExtension and EXAppExtensionAttributes Same — exits cleanly, no descriptors
LSMinimumSystemVersion = 26.0 No change
Strip bundle to one StaticConfiguration widget Same failure — not widget-specific
CODEXBAR_WIDGET_METADATA_MODE=required (generates Metadata.appintents) No change
Swap in xcodebuild-built CodexBarWidget binary instead of swift build one No change — both produce equivalent binaries because CodexBarWidget is .executableTarget, not an extension target

Root cause

Package.swift defines CodexBarWidget as .executableTarget. Both swift build and xcodebuild -workspace .swiftpm/xcode/package.xcworkspace -scheme CodexBarWidget produce the same plain executable; Scripts/package_app.sh then hand-wraps it into an .appex bundle. SwiftPM has no first-class notion of a "widget extension target," so neither path applies the extension-specific linker/entry-point setup that Xcode's WidgetKit template provides. On macOS 26 that template setup is what supplies the blocking runloop.

Suggested fix direction

Add a real Xcode Widget Extension target (com.apple.product-type.app-extension) that compiles Sources/CodexBarWidget/**/*.swift (and the shared CodexBarCore module), and have package_app.sh use the .appex it produces rather than the SwiftPM-built executable. The existing xcodebuild call for App Intents metadata could be the same invocation.

Happy to test fixes — I'm on macOS 26.5 and can iterate.


Additional details

Scripts/compile_and_run.sh:88-97 (Bug 1 location)

export_team_id_from_identity() {
  local identity="${1:-}"
  if [[ -n "${APP_TEAM_ID:-}" || -z "${identity}" ]]; then
    return
  fi
  if [[ "${identity}" =~ \(([A-Z0-9]{10})\)$ ]]; then
    APP_TEAM_ID="${BASH_REMATCH[1]}"
    export APP_TEAM_ID
  fi
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions