You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AppleScript execution is now bounded by a timeout + a fail-fast Automation pre-flight — a stuck call can no longer take the whole MCP server down (#297). Every AppleScript-backed tool funnels through MailController.runScript / runScriptAsList, which called the synchronous, un-timeout'dNSAppleScript.executeAndReturnError. When this binary's Automation grant is .notDetermined (an authorization prompt is pending but cannot be answered in the headless MCP context) — or when Mail is simply unresponsive — that call does not return -1743, it blocks indefinitely: the request thread wedges, and the MCP client's ~120s idle timeout then drops the entire server connection, taking all 53 tools offline rather than failing the one call. Observed repeatedly in a live session: search_emails stayed fast and reliable throughout (it reads the Mail Envelope Index via MailSQLite and never issues an Apple event), while get_special_mailboxes / get_email / reply_email each hung ~120s and then disconnected the server — the "SQLite stable / AppleScript hangs" boundary that pinned the root cause. This is a different failure mode from its two predecessors, and neither of their remedies reaches it: #287 added the zero-TCC open_mailto compose path and #288 rendered actionable guidance for a returned-1743, but both address the denial-that-returns; the never-returns mode has no mailto equivalent on the read/archive side (get_email, batch_export_emails_markdown, save_attachment). Fix, applied at the single shared choke point so all AppleScript tools are covered at once with no per-tool changes: (a) a new private runGuarded<T> runs the execution on a detached thread and waits on a DispatchSemaphore with a bounded deadline — 45s by default, comfortably under the ~120s client idle timeout and far above normal ~1–2s Mail IPC, overridable through a new scriptTimeout test seam — throwing the new MailError.scriptTimedOut(seconds:) on expiry instead of hanging; because NSAppleScript is a blocking, uncancellable C API the timed-out thread is deliberately abandoned (it resolves once TCC is answered or the process restarts), which bounds the block to the deadline rather than pretending the call was cancelled; (b) preflightAutomation() reuses the existing non-promptingAutomationStatus.probe() (#293) to fail fast before blocking — .denied throws reusing AutomationHelp.guidance verbatim (#288 remains the single source of that text) and .targetNotRunning tells the caller to open Mail, while .notDetermined / .granted / unexpected states proceed under the guard (never false-fast a legitimate first-run prompt — that is precisely the state the timeout exists to bound). The new error's message names the pending-prompt / unresponsive-Mail cases explicitly so it is not mistaken for the #288 recorded-Deny wall. Scoped deliberately: SetupWindow's interactive setup script is not an MCP tool path and stays unguarded. New MailControllerTimeoutTests drive the guard through the scriptRunnerOverride seam — a runner that sleeps 5s returns .scriptTimedOut in 0.505s against a 0.5s deadline (bounded, not the runner's 5s and not a wedge), a fast runner returns normally, and a throwing runner's own error propagates rather than being masked as a timeout. 1029 tests pass.