macOS 27 Beta 上 restart-emacs 后窗口出现,但 Dock 持续跳动且无法正常激活

环境:

macOS 27.0 Beta(26A5378n) Emacs 32.0.50 Homebrew emacs-plus@32 NS/macOS GUI 版本

最近在 macOS 27 Beta 上遇到了一个与 restart-emacs 有关的问题。

现象

正常启动 Emacs 没有问题,例如通过 Hammerspoon 或:

open -na /Applications/Emacs.app

启动后窗口、键盘输入和焦点都正常。

但是执行:

M-x restart-emacs

之后会出现以下现象:

  • Emacs 窗口已经显示出来。
  • 鼠标可以点击窗口。
  • Dock 中的 Emacs 图标一直跳动,类似应用仍在等待完成启动。
  • Emacs 无法正常获得键盘焦点。
  • M-x 等键盘操作不可用。
  • Emacs 进程实际上仍然存在,并没有崩溃。

重新编译和安装 Emacs 后问题依然存在;只有彻底结束旧进程,再以新 PID 启动 Emacs 才能恢复正常。

调查结果

对异常状态下的 Emacs 进程执行 sample,可以看到 Emacs 主线程仍然处于正常事件循环:

Frecursive_edit command_loop read_key_sequence read_char wait_reading_process_output NSApplication run

因此它不是普通的启动卡死或 Emacs Lisp 阻塞。

macOS Unified Log 中则可以看到类似的状态变化:

SETFRONT: pid=76505 QUITTING: pid=76505 CHECKIN: pid=76505

也就是说,同一个 PID 被系统记录为正在退出,随后又重新向 LaunchServices 注册。

但是 RunningBoard 仍然将该进程保留在 terminating 状态。之后尝试激活 Emacs 时会出现:

Error Domain=RBSAssertionErrorDomain Code=2 “Specified target process 76505 does not exist”

这与实际表现吻合:Emacs 进程和窗口仍然存在,但 macOS 已经不再将该 PID 视为可以正常激活的前台应用。

可能的原因

Emacs 32 中的 restart-emacs 最终调用:

(save-buffers-kill-emacs nil t)

第二个参数为 t 时,Emacs 会通过 exec 重新执行自身,因此重启前后复用同一个 PID。

目前的推测是:

  1. Emacs 通知 AppKit/LaunchServices 当前应用正在退出。
  2. macOS 将该 PID 标记为 QUITTING 或 terminating。
  3. Emacs 没有真正结束该 PID,而是在同一 PID 内 exec 自身。
  4. 新的 Emacs 再次 CHECKIN。
  5. macOS 27 Beta 没有正确清除该 PID 原来的终止状态。
  6. 最终导致窗口存在,但应用无法正常激活,Dock 图标持续跳动。

这可能是 macOS 27 Beta 的 LaunchServices/RunningBoard 生命周期变化与 Emacs 同 PID 重启机制之间的兼容性问题。

与 Background App Activity 的关系

最初怀疑过 macOS 27 新增的 Background App Activity 管理,但进一步检查后发现:

  • Emacs 不在当前的 Background App Activity 数据库中。
  • Emacs.app/Contents/Info.plist 没有 LSBackgroundOnly。
  • 也没有 LSUIElement。
  • Emacs 本身是普通的前台 GUI App。

因此目前不能认定这是后台运行权限导致的。

RunningBoard 不只管理后台 App,也负责普通前台 App 的启动、退出和激活。更准确地说,这应该是普通 GUI App 生命周期状态在同 PID exec 后没有正确恢复。

临时解决办法

不要使用 Emacs 原本的同 PID exec 重启,而是:

  1. 让当前 Emacs 正常退出。
  2. 等待旧 PID 完全消失。
  3. 通过 open -na 启动一个全新 PID 的 Emacs。

目前使用的 Emacs Lisp workaround:

  ;; 2026-07-15: Work around a macOS 27 beta LaunchServices/RunningBoard issue.
  ;; `restart-emacs' reuses the current PID, which can leave the relaunched Emacs
  ;; visible but unable to activate while its Dock icon keeps bouncing.  Wait for
  ;; the old process to exit completely, then launch a fresh app process.
  (setup files
    (defun +restart-emacs-after-exit ()
      "Launch a new macOS Emacs after the current process exits."
      (remove-hook 'kill-emacs-hook #'+restart-emacs-after-exit)
      (call-process
       "/usr/bin/nohup" nil 0 nil
       "/bin/sh" "-c"
       (format
        "while /bin/kill -0 %d 2>/dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open -na %s"
        (emacs-pid)
        (shell-quote-argument "/Applications/Emacs.app"))))

    (define-advice restart-emacs (:override () macos-safe-relaunch)
      "Restart Emacs without reusing the current process ID."
      (interactive)
      (add-hook 'kill-emacs-hook #'+restart-emacs-after-exit 100)
      (unwind-protect
          (save-buffers-kill-emacs)
        (remove-hook 'kill-emacs-hook #'+restart-emacs-after-exit))))

使用该方案后,新的 Emacs 拥有全新 PID,可以正常完成 LaunchServices 注册,Dock、焦点和键盘输入均恢复正常。

2 个赞

太硬核了

直接restart 从CLI启动的GUI emacs 会导致环境变量全部丢失,所以我每次都是老老实实从cli启动

为什么要 restart, 什么使用场景

测试配置等都需要啊