I use lazy.nvim (a.k.a. lazy) for package management as part of the larger LazyVim distribution for Neovim. By default1, lazy checks for updates periodically, and applying all the updates is as simple as pressing uppercase U in the lazy UI.

This makes it really easy to keep up to date as it lets me follow new innovations in the Neovim ecosystem and try out new ways of doing things. But it has its downsides — namely instability, conflicts and bugs! This may sound tiresome enough to forgo regular updates entirely, but with the right coping and troubleshooting strategies, I’ve figured out how to have my cake and eat it too.

Level 0: Handle a Buggy Plugin

If a plugin has already been pinpointed as the source of the problem, it can be dealt with in a few different ways.

The trivial option: simply uninstall it
If it was installed manually and is no longer wanted, simply uninstall it by removing the relevant spec from lazy config, then restart Neovim.

Tell lazy to disable it
If it came preconfigured with a distribution that uses lazy (e.g. LazyVim) it can’t be removed directly, but it can be disabled. Just define the spec as follows, restart, and lazy will no longer load it:

{ "some/troublesome-plugin.nvim", enabled = false }

Rollback and pin it
If it’s still needed, but only an older version works properly, go through the plugin’s commit history and pick a commit that hopefully doesn’t exhibit the problem. Then define the spec as follows:

{ 
  "some/troublesome-plugin.nvim", 
  -- The hash of the commit to pin to
  commit = "d9328ef903168b6f52385a751eb384ae7e906c6f",
  ...
}

Open the lazy UI and run an “update” on this plugin by cursoring over the plugin and pressing lowercase u. The plugin will roll back to the specified commit, and will be in its rolled back state after restarting Neovim. If the pinned commit still doesn’t work, repeat the process with an older commit hash until the issue is resolved.

Level 1: Backup and Restore

Lazy maintains a lazy-lock.json lockfile in the nvim config directory2, which captures a snapshot of the entire Neovim configured environment at the current point in time. When used properly, this file allows lazy to restore previously working snapshots when a new wave of updates breaks something.

To fully take advantage of this, the nvim directory — including the lazy-lock.json file — needs to be committed to a git repository. A commit should be made whenever an update results in a reasonably stable Neovim environment, or before downloading new updates. If git isn’t set up, at least save a backup of the lazy-lock.json file somewhere safe prior to an update.

If something breaks and it’s not clear which plugin was responsible, discard the changes to the lazy-lock.json file (or otherwise restore it from a backup location) then simply press R in the lazy UI to restore all plugins back to the versions as defined in lazy-lock.json. After a Neovim restart, things should be back to the way they were before the update, and now the list of new updates can be reviewed for any troublemakers.

Don't be afraid to commit regularly

Having a regular history of committed snapshots of lazy-lock.json greatly helps with walking back in time to find a workable config, even if some of those snapshots were imperfect and somewhat broken.

Level 2: Test a Plugin in Isolation

When it isn’t totally clear where an issue might originate, it can help to try to replicate the issue with a plugin in a minimal environment. This distinguishes plugin bugs (which can be reported to the plugin maintainer) from personal configuration conflicts or mistakes. To do this, create a Lua file as follows:

repro.lua
vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
 
require("lazy.minit").repro({
  spec = {
    { 
      "some/troublesome-plugin.nvim",
      -- Whatever minimal and basic config is required to initialise the plugin.
      -- Try not to put your preferential plugin config here yet.
    }
  },
})

Then start a Neovim instance with this sole file loaded as its config via nvim -u repro.lua.

If the issue can be replicated, then the plugin is likely fundamentally broken at its current version. If not, gradually add more configuration options to repro.lua to trigger the error condition.

require("lazy.minit").repro({
  spec = {
    { 
      "some/troublesome-plugin.nvim",
      opts = {
        -- opts from personal config's spec
      },
      config = function(_, opts) 
        -- any other code from personal config's spec
        require("troublesome-plugin").setup(opts)
      end
    }
  },
})

If the issue can’t be replicated in this isolated environment, then it likely points to a conflict with another plugin or setting (e.g. keymaps, options, autocmds).

Level 3: Test Against Default LazyVim

To distinguish between personal config issues and those arising from LazyVim’s ecosystem of plugins and settings, use the same repro.lua file from Level 2: Test a Plugin in Isolation to load in a totally vanilla LazyVim environment with all the default bells and whistles.

repro.lua
vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
 
require("lazy.minit").repro({
  spec = {
    { "LazyVim/LazyVim", import = "lazyvim.plugins" },
    -- optionally, import any extras modules here
    -- e.g. { import = "lazyvim.plugins.extras.lang.typescript" },
    { 
      "some/troublesome-plugin.nvim",
      -- Whatever minimal and basic config is required to initialise the plugin
    }
  },
})

When nvim -u repro.lua is run, it will do a full install3 of all default LazyVim plugins in addition to the suspected troublesome plugin.

If the issue can be replicated at this stage, then it’s likely not an issue with personal configuration settings, and other users of LazyVim may soon chime in with bug reports. At this stage, it could also be fruitful to start disabling certain LazyVim bundled plugins to pinpoint the conflicting area.

If the issue still cannot be replicated, then the blame likely lies within the personal config (i.e. nvim) folder.

Level 4: Dissect a Personal Config

We go back to a personal config that exhibits some brokenness. What can we do?

I recommend commenting out whole files or sections of config until the problem goes away, which would then indicate the problem area. There’s no quick way to do this, and it relies heavily on intuition.

Assuming it’s a LazyVim config, some areas to consider commenting out:

  • config/keymaps.lua
  • config/options.lua
  • config/autocmds.lua
  • the line { import = "plugins" } in config/lazy.lua
    (disables all custom plugin configs in one fell swoop)
  • any LazyVim extra modules that were enabled in the same section
  • the inside of a spec in any of the files inside ./plugins/
    (lets us keep the file and the config code, but will temporarily disable that plugin)

Hopefully one of these will be the difference between a working config and a broken one!

Footnotes

  1. For more stability and less temptation to download every single update, this behavior can be turned off by setting checker { enabled = false } to false in lazy.lua

  2. See :h standard-path for where the nvim config directory lives

  3. Don’t worry — the plugins will be installed to a separate .repro folder, so it won’t mess up the actual nvim-data folder where the real instance of Neovim and its plugins run from.