Skip to content

Caveats with after‐init‐hook and emacs‐startup‐hook

Kristoffer Balintona edited this page May 24, 2025 · 1 revision

elpaca-after-init-hook

Elpaca does its work asynchronously, after reading your init file. These means after-init-hook and emacs-startup-hook are run before packages are activated. Elpaca provides elpaca-after-init-hook, which is run after all queues in the init file are processed. You can add the following to the end of your init file if you must rely on after-init-hook or emacs-startup-hook, but it is better to use elpaca-after-init-hook:

;;END OF INIT FILE
;; prevents `elpaca-after-init-hook` from running more than once.
(setq elpaca-after-init-time (or elpaca-after-init-time (current-time)))
(elpaca-wait)

Using desktop.el and desktop-save-mode

Users will notice that a configuration like the following:

(use-package desktop
  :ensure nil
  :config
  (desktop-save-mode 1))

may frequently cause issues. Namely, when desktop-save-mode is enabled immediately when desktop.el is loaded, desktop will automatically call desktop-read to restore your buffer. The problem is that this call to desktop-read occurs in the after-init-hook: we see the code responsible for this at the end of desktop.el:

;; ----------------------------------------------------------------------------
;; When `desktop-save-mode' is non-nil and "--no-desktop" is not specified on the
;; command line, we do the rest of what it takes to use desktop, but do it
;; after finishing loading the init file.
;; We cannot use `command-switch-alist' to process "--no-desktop" because these
;; functions are processed after `after-init-hook'.
(add-hook
  'after-init-hook
  (lambda ()
    (let ((key "--no-desktop"))
      (when (member key command-line-args)
        (setq command-line-args (delete key command-line-args))
        (desktop-save-mode 0)))
    (when desktop-save-mode
      (desktop-read)
      (setq inhibit-startup-screen t))))

Problems arise because desktop may restore buffers prior to elpaca having fully initialized and loaded user configurations. Consequently, for example, additions to org-mode-hook may not run because desktop opened your org buffers prior to those additions being run by elpaca.

To resolve this, we may do something like the following:

(use-package desktop
  :ensure nil
  :hook
  (elpaca-after-init-hook
   . (lambda ()
       (desktop-save-mode 1) ; Enable the mode right before
       (let ((key "--no-desktop"))
         (when (member key command-line-args)
           (setq command-line-args (delete key command-line-args))
           (desktop-save-mode 0)))
       (when desktop-save-mode
         (desktop-read)
         (setq inhibit-startup-screen t)))))

Effectively, we have added the same function desktop.el adds to after-init-hook but to elpaca-after-init-hook. But we must be sure to ensure that desktop-save-mode is called only right before desktop-read is called --- in the above, we place it prior to the let form --- instead of in e.g. :config, for doing so will then cause desktop.el to still restore buffers via after-init-hook.

With this solution, we preserve the behavior we would have had without elpaca, including respecting the --no-desktop flag passed to Emacs.

Clone this wiki locally