Skip to content

v2.24.0

Latest

Choose a tag to compare

@kiki830621 kiki830621 released this 25 Jul 16:10

Fixed

  • 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'd NSAppleScript.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-prompting AutomationStatus.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.