Skip to content

Instantly share code, notes, and snippets.

@hlissner
Last active August 31, 2026 06:59
Show Gist options
  • Select an option

  • Save hlissner/82901750d8701e62872f1339e62c01dd to your computer and use it in GitHub Desktop.

Select an option

Save hlissner/82901750d8701e62872f1339e62c01dd to your computer and use it in GitHub Desktop.
Doom Emacs: troubleshooting guide

< back to index

Important

This gist is temporary until the new documentation (docs.doomemacs.org) and community wiki (wiki.doomemacs.org) have been published.

Troubleshooting/Debugging Guide

When issues arise (and this is Emacs, they will), it pays to know how to use the diagnostic tools that are available to you.

The *Messages* buffer

Emacs may occasionally display a message in the echo area (the minibuffer at the bottom of the Emacs frame):

image

Emacs keeps a log of these messages in a hidden buffer named *Messages*. When you encounter an issue, it is a good idea to visit this buffer and search for warnings and error messages---or any suspicious activity surrounding the problematic event. With luck, it may offer some clues as to where you should look next.

The *Messages* buffer can be accessed with either:

  • M-x view-echo-area-messages
  • M-x switch-to-buffer RET *Messages*.
  • C-he
  • SPChe (for evil users)

If you find an error message, see dealing with errors below.

Dealing with errors

Emacs produces errors that can be cryptic for the uninitiated. Learning to interpret and debug them takes practice. Your next step should always be to produce a backtrace from the error(s) and study them.

Important

A backtrace lays out the series of function calls that led to the error, and can tell you much more about why that error happened than the error message by itself.

Once you have a backtrace, investigate the functions themselves using the tips for inspecting source code below.

Backtraces

Eventually, you will encounter a cryptic error while using Emacs, but more information can be extracted from errors if you produce a backtrace from it. This should be your first step when dealing with any error you don't immediately recognize --- you will always be asked for one in bug reports, and they may help you locate and resolve the issue yourself.

A backtrace lays out the series of function calls that led to the error, and what arguments they were called with. This can tell you much more about where in Doom's, Emacs', or a package's code base an error originated from.

How to make sense of a backtrace

For the sake of demonstration, here is the backtrace of an error I artificially created by typing M-x eval-expression RET (+ 1 nil):

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p nil)
  +(1 nil)
  eval-expression((+ 1 nil) nil nil 127)
  funcall-interactively(eval-expression (+ 1 nil) nil nil 127)
  command-execute(eval-expression record)
  execute-extended-command(nil "eval-expression" "eval-expression")
  funcall-interactively(execute-extended-command nil "eval-expression" "eval-expression")
  command-execute(execute-extended-command)

Let me break this down:

  1. The first line will always be in the format:

    Debugger entered--Lisp error: (ERROR_TYPE [EXTRA_DATA...])
    

    ERROR_TYPE is the name of the error. Emacs lists its internal errors in its manual. The exact format of EXTRA_DATA varies from error to error, however.

    In any case, here is what you can glean from this particular error, (wrong-type-argument number-or-marker-p nil):

    1. wrong-type-argument indicates a data type error. This means something in the expression (+ 1 nil) was expecting data of one type, but received data in another, illegal type, and cannot proceed.

    2. The first argument of a wrong-type-argument error is the name of a predicate function that the data failed to satisfy. In this case, number-or-marker-p. This suggests a number (or marker, but I won't go into that here) was expected, and the actual data given was neither.

    3. The nil is the illegal data that was received instead of a number or marker.

  2. The next line of the backtrace is +(1 nil), and it will always be the block of code emitted the error.

    To spoil the mystery, + expects all its arguments to be numeric, so passing it a non-number will cause this type error.

    [!NOTE] C-h f+ will reveal the + function's documentation, which says:

    Return sum of any number of arguments, which are numbers or markers.

  3. The rest of the backtrace tells you the series of function calls that led to this point. For this contrived example they aren't important, but more complex errors may require you work further backwards to find the true cause.

    In those cases, I suggest you read "Inspecting source code" for ways to directly inspect functions in the backtrace.

How to produce a backtrace

Simply, you set the debug-on-error variable to a non-nil value. Here are three ways to do so:

  • Start Emacs with emacs --debug-init. Use this for errors that occur at startup

  • Doom Emacs users should activate doom-debug-mode:

    • Evil users: SPChdd
    • Users that have disabled evil: C-hdd.
    • Otherwise: M-x doom-debug-mode
  • If the above don't work, or you don't use Doom, use M-x toggle-debug-on-error, instead.

Once debug-on-error is activated, go ahead and reproduce an error, and a new window will appear to display a backtrace.

Warning

Unfortunately, not all errors produce a backtrace. For example: when the error is emitted by the modeline or a post-command-hook function, Emacs suppresses them. This is done to protect against really destructive errors breaking Emacs' UI.

When this happens let us know in your bug report, but try to include the full error message that appears in the *Messages* buffer. It may, at least, mention where the error was suppressed.

From bin/doom

If the error you encountered was emitted from bin/doom, a limited backtrace will already be displayed, but the full backtrace is written to ~/.emacs.d/.local/state/cli.doom.*.*.error. Attach this file to your bug report.

From frozen Emacs

If Emacs freezes or hangs, you can produce a backtrace to determine what logic Emacs is hanging on with one of the following:

  • Send a USR2 signal to the Emacs process: pkill -USR2 emacs or kill -USR2 $EMACSPID.

    [!TIP] EMACSPID should be replaced with the process id of your frozen instance of Emacs. Use the commands pgrep emacs or ps aux | grep emacs to help you locate its PID.

  • Turn on debug-on-quit, mash C-g, and hope for the best.

Warning

Not all freezes can be recovered from, unfortunately. If the above doesn't work, your only option is to forcibly kill the Emacs process.

Looking up documentation

Emacs is a self-documenting editor. Documentation is embedded into its source, and Emacs provides tools not only to look it up, but to inspect Emacs' state at any time. One way to do this is with it's describe-* family of commands.

Note

Doom replaces the describe-* family with helpful.el. We'll be using those instead.

For example:

  • M-x helpful-function RET add-hook --- show documentation for the add-hook function, including usage examples, its definition, how it's advised (if at all), and then some.
  • M-x helpful-variable --- do the same, but with variables. Use it to inspect variables or change their values.
  • M-x helpful-key --- this will tell you what's bound to any arbitrary key sequence, including their documentation.
  • M-x doom/help-custom-variable --- like helpful-variable but lists only variables that Emacs and packages intend for you to customize (it will omit private/internal variables).

Doom users will find these commands under SPCh---for evil users---and C-h for everyone else.

Searching Doom's documentation

On top of Emacs documentation, Doom comes with its own manual. There are commands for accessing and searching Doom's manual from within Emacs:

  • M-x doom/docs-search -- Search all text in the manual.
  • M-x doom/docs-headings -- Search only headlines in our manual.

Profiling and benchmarking

Sometimes, Emacs can be slow, but if you can zero in on what's responsible, perhaps it can be addressed. To do so, Emacs has a built-in profiler that can be toggled via M-x doom/toggle-profiler. Invoke it once to start it, and again to produce a report.

image

Tip

Additionally, explain-pause-mode may be used to profile running tasks or functions in emacs, similar to the unix program top that profiles running processes. Once activated, this minor-mode displays an auto-updating tabular buffer of running functions that updates every second. See https://github.com/lastquestion/explain-pause-mode for more information about how to use this minor mode.

Evaluating elisp on-the-fly

Save yourself the trouble of a restart and evaluate elisp code on the spot with one of the following commands:

  • If you have Evil enabled:
    • gr (+eval:region) --- an evil operator that evaluates the selected or indicated region of code and display the return value in a popup overlay or buffer.
    • gR (+eval/buffer) --- evaluate the whole buffer.
  • If you don't:
    • C-ce (+eval/buffer-or-region) --- evaluate the whole buffer or selected region.

Or evaluate elisp directly in the ielm REPL:

  • If you have Evil enabled:
    • SPCor (+eval/open-repl-other-window) --- open a REPL for the current language.
  • If you don't:
    • C-cor (+eval/open-repl-other-window) --- open a REPL for the current language.

Warning

Changes to ~/.doom.d/packages.el or your doom! block in ~/.doom.d/init.el require a restart (and doom sync) to take effect.

Testing elisp/packages in vanilla Emacs

"The sandbox" is a feature of Doom Emacs. It is a test bed for running elisp in a fresh instance of Emacs with varying amounts of Doom loaded (none at all, all of it, or somewhere in between). This can help you isolate bugs and determine who they should be reported to.

If a bug can be recreated in vanilla Emacs than it should be reported upstream, either to the developers of the relevant packages or, perhaps, the Emacs devs themselves.

Open the sandbox

There are three common ways to access the sandbox:

  • SPChE (for evil users)

  • C-hE (for non-evil users)

  • M-x doom/sandbox

These will pop up a *doom:sandbox* buffer in emacs-lisp-mode. Anything entered into this buffer will be executed in the sandbox when it is launched.

Launch the sandbox

There are four ways to launch the sandbox:

  • C-cC-c -- launches vanilla Emacs. Vanilla means nothing is loaded; purely Emacs and nothing else. If you can reproduce an error here, then the issue likely lies in the plugin(s) you are testing or in Emacs itself.

  • C-cC-d -- launches "vanilla Doom", which is vanilla Emacs plus Doom's core. This does not load your private config, nor any of Doom's (or your) modules.

  • C-cC-p -- launches "vanilla Doom+". That is, Doom core plus the modules that you have specified in the doom! block of your private config (in =~/.doom.d/init.el=). This does not load your private config, however.

  • C-cC-f -- launches "full Doom". It loads Doom's core, your enabled modules, and your private config. This instance should be identical to the instance you launched it from.

Test packages in the sandbox

Sandbox instances of Emacs will inherit your load-path. This way you can load packages -- even in Vanilla Emacs -- without worrying about installing or setting them up. In most cases, all you have to do is (require PACKAGE) to load the package, then you can use it right away. For example:

(require 'magit)
(find-file "~/some/file/in/a/repo")
(call-interactively #'magit-status)

Bisecting your private config

So something broke your config and you're observing odd behavior or inscrutable error messages. All other options have been exhausted---what do?

Try bisecting your config, or the responsible Doom modules, one of two ways:

  1. Walk through the git history of your private config or Doom Emacs with git bisect.

  2. Or with comments:

    This step is as simple as it sounds: you comment out half of your config in $DOOMDIR/config.el, restart Emacs, and try to reproduce your issue. Rinse and repeat until you've isolated the problematic lines. Better yet, drill deeper into those lines and try bisecting those functions, and so on.

    [!WARNING] Some folks stop bisecting at toggling modules in $DOOMDIR/init.el, but this is rarely helpful. It's better to open the module's config.el with SPCfe modules/some/module/config.el and bisect the module's innards instead.

    Remember to consult a backtrace, if you can get one! The more you narrow it down before you bisect, the less bisecting work you'll have to do!

Inspecting source code

Sometimes your best bet is to look at the source. While the helpful-* commands above will display documentation for functions and variables, you can use the following commands to jump directly to them:

  • M-x find-library -- to jump to an installed package's source.
  • M-x find-function -- to jump to a function's definition.
  • M-x find-variable -- to jump to a variable's origin.

Tip

Evil users can press g d (and for non-evil users: C-c c d) to jump to the definition of the function or variable under your cursor.

Otherwise, you can preform a text search of all loaded packages with one of:

  • M-x doom/help-search-load-path -- perform a text search on all packages in load-path.
  • M-x doom/help-search-loaded-files -- perform a text search on all currently-loaded *.el files.

Another option is to jump directly to a file Doom Emacs' source code:

  • M-x +default/find-in-emacsd -- jump to a file in Doom's source (in ~/.emacs.d)
  • M-x +default/browse-emacsd -- browse Doom's source (in ~/.emacs.d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment