fix: request macOS notifications from the main queue

The Rust command now keeps the synchronous bridge wait off the invoke thread, but the Objective-C bridge still initiated UserNotifications authorization from that worker. In a packaged app that path could fail immediately and the settings UI collapsed the error into an unsupported environment.

Start notification settings reads, authorization requests, and delivery submissions on the macOS main queue while keeping the blocking wait on the worker thread. This preserves foreground-safe notification delivery without deadlocking the UI or losing the system permission prompt.

Constraint: UserNotifications APIs are UI-adjacent and should be initiated on the app main queue.
Rejected: Only change the unsupported status copy | it would keep the authorization path broken.
Confidence: high
Scope-risk: narrow
Directive: Keep UserNotifications calls main-queue initiated while the synchronous wait remains off the main thread.
Tested: cd desktop/src-tauri && cargo test
Tested: bun run check:native
Tested: SKIP_INSTALL=1 ./scripts/build-macos-arm64.sh
Tested: codesign --verify --deep --strict --verbose=2 desktop/build-artifacts/macos-arm64/Claude Code Haha.app
Tested: hdiutil verify desktop/build-artifacts/macos-arm64/Claude Code Haha_0.2.1_aarch64.dmg
Tested: Computer Use opened /Users/nanmi/workspace/myself_code/claude-code-haha/desktop/build-artifacts/macos-arm64/Claude Code Haha.app and verified Settings -> General shows notifications as authorized after disable/enable.
Not-tested: Fresh first-time system notification prompt on a machine that has never granted this bundle identifier.
This commit is contained in:
程序员阿江(Relakkes) 2026-05-08 09:43:25 +08:00
parent de9d044bdb
commit 7796fec691

View File

@ -78,6 +78,15 @@ static void cchh_copy_error(char *buffer, uintptr_t buffer_len, NSString *messag
buffer[max_len] = '\0';
}
static void cchh_run_on_main_sync(dispatch_block_t block) {
if ([NSThread isMainThread]) {
block();
return;
}
dispatch_sync(dispatch_get_main_queue(), block);
}
void cchh_set_notification_response_callback(CCHHNotificationResponseCallback callback) {
cchhNotificationResponseCallback = callback;
}
@ -116,10 +125,12 @@ int cchh_notification_authorization_status(char *error_buffer, uintptr_t error_b
__block NSInteger status = UNAuthorizationStatusNotDetermined;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[cchh_notification_center() getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
status = settings.authorizationStatus;
dispatch_semaphore_signal(semaphore);
}];
cchh_run_on_main_sync(^{
[cchh_notification_center() getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
status = settings.authorizationStatus;
dispatch_semaphore_signal(semaphore);
}];
});
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)) != 0) {
cchh_copy_error(error_buffer, error_buffer_len, @"Timed out while reading macOS notification permission");
@ -143,11 +154,13 @@ bool cchh_request_notification_authorization(char *error_buffer, uintptr_t error
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound;
[cchh_notification_center() requestAuthorizationWithOptions:options completionHandler:^(BOOL isGranted, NSError *error) {
granted = isGranted;
requestError = error;
dispatch_semaphore_signal(semaphore);
}];
cchh_run_on_main_sync(^{
[cchh_notification_center() requestAuthorizationWithOptions:options completionHandler:^(BOOL isGranted, NSError *error) {
granted = isGranted;
requestError = error;
dispatch_semaphore_signal(semaphore);
}];
});
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)) != 0) {
cchh_copy_error(error_buffer, error_buffer_len, @"Timed out while requesting macOS notification permission");
@ -195,10 +208,12 @@ bool cchh_send_user_notification(const char *title, const char *body, const char
__block NSError *deliveryError = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[cchh_notification_center() addNotificationRequest:request withCompletionHandler:^(NSError *error) {
deliveryError = error;
dispatch_semaphore_signal(semaphore);
}];
cchh_run_on_main_sync(^{
[cchh_notification_center() addNotificationRequest:request withCompletionHandler:^(NSError *error) {
deliveryError = error;
dispatch_semaphore_signal(semaphore);
}];
});
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)) != 0) {
cchh_copy_error(error_buffer, error_buffer_len, @"Timed out while delivering macOS notification");