Trigger the Mail consent prompt via a real Apple Event, not the pre-flight API
Rune's log capture proved the theory wrong: AEDeterminePermissionToAutomate- Target(askUserIfNeeded: true) returned -1743 in ~9ms — far too fast for any real dialog to have been shown and answered — and the very next passive status check still reported -1744 (not yet determined), meaning the OS never actually recorded a decision despite the "denied" return. That's the pre-flight API misbehaving, not a threading issue (both previous fixes addressed threading and neither helped). requestAccess() now attempts a real, harmless Apple Event (listAccounts) via the same runHandler path mail_list_accounts/testConnection() already use — sending an actual Apple Event is the standard, proven mechanism for triggering macOS's first-time Automation consent prompt, and this path is already confirmed working (it's what correctly reported "not authorized" in the first live test).
This commit is contained in:
@@ -79,24 +79,31 @@ final class AppleMailService {
|
|||||||
/// Triggers the real macOS "Confab wants to control Mail" Automation permission prompt if
|
/// Triggers the real macOS "Confab wants to control Mail" Automation permission prompt if
|
||||||
/// the user hasn't been asked yet (a no-op if already granted or denied).
|
/// the user hasn't been asked yet (a no-op if already granted or denied).
|
||||||
///
|
///
|
||||||
/// First attempt just called this synchronously from an `async` method, reasoning that
|
/// Two earlier attempts used `AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true)` —
|
||||||
/// `-default-isolation=MainActor` makes it run on the main actor anyway — it didn't work live
|
/// first called directly (relying on implicit MainActor isolation), then wrapped in
|
||||||
/// (button flashed, no dialog, no state change, confirmed still broken even after a
|
/// `DispatchQueue.main.async`. Neither worked live: logs showed it returning -1743 in ~9ms,
|
||||||
/// `tccutil reset`). Being "MainActor-isolated" inside Swift's structured-concurrency/Task
|
/// far too fast for any real human response, immediately followed by the passive status check
|
||||||
/// execution model is NOT the same as a classic synchronous AppKit call — this blocking,
|
/// (`askUserIfNeeded: false`) still reporting -1744 ("would need to ask") — i.e. the OS never
|
||||||
/// modal-dialog-presenting legacy C API needs to run from a genuine `DispatchQueue.main.async`
|
/// actually recorded a decision despite the "denied" return. That points at
|
||||||
/// dispatch (the same execution shape as a normal button target-action), not from inside a
|
/// `AEDeterminePermissionToAutomateTarget` itself misbehaving in this context (possibly a
|
||||||
/// suspended `Task` continuation frame, to correctly nest its own run loop for the prompt.
|
/// macOS 27 beta issue — this project has hit other real TCC/permission regressions on this
|
||||||
|
/// beta before, see CLAUDE.md's Personal Data Tools Apple Feedback note), not a threading bug.
|
||||||
|
///
|
||||||
|
/// Switched strategy entirely: attempt a real, harmless Apple Event via the same `runHandler`
|
||||||
|
/// path `mail_list_accounts`/`testConnection()` already use — sending an actual Apple Event is
|
||||||
|
/// the standard, well-proven way macOS shows the first-time "X wants to control Y" Automation
|
||||||
|
/// prompt, and this exact path is already confirmed working (it's what correctly reported
|
||||||
|
/// "not authorized" in the very first live test).
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func requestAccess() async -> Bool {
|
func requestAccess() async -> Bool {
|
||||||
Log.mail.info("requestAccess: called")
|
Log.mail.info("requestAccess: called — attempting a real Apple Event (listAccounts) to trigger the OS consent prompt")
|
||||||
return await withCheckedContinuation { continuation in
|
switch await runHandler("listAccounts", arguments: []) {
|
||||||
DispatchQueue.main.async {
|
case .success:
|
||||||
Log.mail.info("requestAccess: about to call AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true)")
|
Log.mail.info("requestAccess: real Apple Event succeeded — access granted")
|
||||||
let status = Self.checkMailAutomationPermission(askUserIfNeeded: true)
|
return true
|
||||||
Log.mail.info("requestAccess: AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true) returned \(status)")
|
case .failure(let error):
|
||||||
continuation.resume(returning: status == 0)
|
Log.mail.info("requestAccess: real Apple Event failed — \(String(describing: error))")
|
||||||
}
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user