Repository: rmagatti/auto-session (lua)

Issue:
Hidden checkhealth buffers saved and restored as blank buffers
**Describe the bug**
After running `:checkhealth auto-session` (or any `checkhealth` command) the checkhealth buffer gets saved (even though it's hidden). When the session is restored, that buffer is restored as a non-hidden blank buffer named `health://`

**To Reproduce**
Steps to reproduce the behavior:
1. have a normal buffer open, e.g. `a.txt`
2. run `:checkhealth auto-session`
3. close the check health window
4. save the session

Session file contains

```viml
badd +1 a.txt
badd +1 health://
```

5. restore the session

**Expected behavior**
Expected: only the original normal buffer is restored
Actual: a `health://` buffer is restored along with the original buffer

**Screenshots**
Buffers before opening checkhealth:
<img width="629" height="124" alt="Image" src="https://github.com/user-attachments/assets/d80e0030-7331-4d38-87a2-adb04d920dc9" />

Buffers after closing checkhealth window:
<img width="648" height="188" alt="Image" src="https://github.com/user-attachments/assets/bef82449-4e40-4e0e-bc5c-d4c0ba6e77d1" />

Buffers after restoring the session:
<img width="627" height="97" alt="Image" src="https://github.com/user-attachments/assets/2ab0e994-53e3-42aa-9c17-6c7b8e11287c" />

**Baseline (please complete the following information):**
nvim 0.11.2
```lua
vim.o.sessionoptions = 'blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions'
```


**Solution**
My proposed solution is to add a config option:

```lua
  close_filetypes_on_save = { "checkhealth" }, -- List of file types to close before saving
```

And close those buffers before saving a session. Users could add other filetypes if desired.

Pull request description:
Fixes #447

I had some time so I made the changes to #448 (except for the minimum version change).

@rmagatti feel free to close if this isn't how you want it to be.

Also, got embedded stylua formatting set up so there are some formatting cleanups to the README

Patch:
diff --git a/README.md b/README.md
index e3fdaad8..d24d3388 100644
--- a/README.md
+++ b/README.md
@@ -11,17 +11,17 @@ AutoSession takes advantage of Neovim's existing session management capabilities
 [Lazy.nvim](https://github.com/folke/lazy.nvim):
 
 ```lua
-{
-  'rmagatti/auto-session',
+return {
+  "rmagatti/auto-session",
   lazy = false,
 
   ---enables autocomplete for opts
   ---@module "auto-session"
   ---@type AutoSession.Config
   opts = {
-    suppressed_dirs = { '~/', '~/Projects', '~/Downloads', '/' },
+    suppressed_dirs = { "~/", "~/Projects", "~/Downloads", "/" },
     -- log_level = 'debug',
-  }
+  },
 }
 ```
 
@@ -29,12 +29,12 @@ AutoSession takes advantage of Neovim's existing session management capabilities
 
 ```lua
 use {
-  'rmagatti/auto-session',
+  "rmagatti/auto-session",
   config = function()
     require("auto-session").setup {
-      suppressed_dirs = { "~/", "~/Projects", "~/Downloads", "/"},
+      suppressed_dirs = { "~/", "~/Projects", "~/Downloads", "/" },
     }
-  end
+  end,
 }
 ```
 
@@ -57,7 +57,7 @@ Manually saving a session can still be done by calling `:SessionSave`.
 Here are the default settings:
 
 ```lua
-{
+opts = {
   enabled = true, -- Enables/disables auto creating, saving and restoring
   root_dir = vim.fn.stdpath "data" .. "/sessions/", -- Root dir where sessions will be stored
   auto_save = true, -- Enables/disables auto saving session on exit
@@ -70,6 +70,7 @@ Here are the default settings:
   git_auto_restore_on_branch_change = false, -- Should we auto-restore the session when the git branch changes. Requires git_use_branch_name
   lazy_support = true, -- Automatically detect if Lazy.nvim is being used and wait until Lazy is done to make sure session is restored correctly. Does nothing if Lazy isn't being used. Can be disabled if a problem is suspected or for debugging
   bypass_save_filetypes = nil, -- List of filetypes to bypass auto save when the only buffer open is one of the file types listed, useful to ignore dashboards
+  ignore_filetypes_on_save = { "checkhealth" }, -- List of filetypes to close buffers of before saving a session, ignores checkhealth by default
   close_unsupported_windows = true, -- Close windows that aren't backed by normal file before autosaving a session
   args_allow_single_directory = true, -- Follow normal session save/load logic if launched with a single directory as the only argument
   args_allow_files_auto_save = false, -- Allow saving a session even when launched with a file argument (or multiple files/dirs). It does not load any existing session first. While you can just set this to true, you probably want to set it to a function that decides when to save a session when launched with file args. See documentation for more detail
@@ -108,7 +109,7 @@ For a better experience with the plugin overall using this config for `sessionop
 **Lua**
 
 ```lua
-vim.o.sessionoptions="blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions"
+vim.o.sessionoptions = "blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions"
 ```
 
 **VimL**
@@ -154,15 +155,14 @@ If you create a manually named session via `SessionSave my_session` or you resto
 You can use Telescope or [snacks.nvim](https://github.com/folke/snacks.nvim) to see, load, and delete your sessions. The configuration options are in the `session_lens` section:
 
 ```lua
-
-{
-  'rmagatti/auto-session',
+return {
+  "rmagatti/auto-session",
   lazy = false,
   keys = {
     -- Will use Telescope if installed or a vim.ui.select picker otherwise
-    { '<leader>wr', '<cmd>SessionSearch<CR>', desc = 'Session search' },
-    { '<leader>ws', '<cmd>SessionSave<CR>', desc = 'Save session' },
-    { '<leader>wa', '<cmd>SessionToggleAutoSave<CR>', desc = 'Toggle autosave' },
+    { "<leader>wr", "<cmd>SessionSearch<CR>", desc = "Session search" },
+    { "<leader>ws", "<cmd>SessionSave<CR>", desc = "Save session" },
+    { "<leader>wa", "<cmd>SessionToggleAutoSave<CR>", desc = "Toggle autosave" },
   },
 
   ---enables autocomplete for opts
@@ -200,11 +200,10 @@ You can use Telescope or [snacks.nvim](https://github.com/folke/snacks.nvim) to
         -- },
       },
 
-
       -- Telescope only: If load_on_setup is false, make sure you use `:SessionSearch` to open the picker as it will initialize everything first
       load_on_setup = true,
     },
-  }
+  },
 }
 ```
 
@@ -233,8 +232,10 @@ There are two config options, `allowed_dirs` and `suppressed_dirs`, that control
 Both options are a table of directories, with support for globs:
 
 ```lua
-  allowed_dirs = { '/some/dir/', '/projects/*' }
-  suppressed_dirs = { '/projects/secret' }
+opts = {
+  allowed_dirs = { "/some/dir/", "/projects/*" },
+  suppressed_dirs = { "/projects/secret" },
+}
 ```
 
 With those options, sessions would only be auto-saved for `/some/dir` and any direct child of `/projects` (e.g. `/projects/myproject` but not `/projects/myproject/submodule`) except `/projects/secret`
@@ -262,22 +263,21 @@ Now when you changes the cwd with `:cd some/new/dir` AutoSession handles it grac
 Hooks are available for custom actions _before_ and _after_ the `cwd` is changed. Here's the config for tracking cwd and a hook example:
 
 ```lua
-require('auto-session').setup({
-  suppressed_dirs = { '~/', '~/Projects', '~/Downloads', '/' },
+require("auto-session").setup {
+  suppressed_dirs = { "~/", "~/Projects", "~/Downloads", "/" },
 
-  cwd_change_handling = true
+  cwd_change_handling = true,
 
   pre_cwd_changed_cmds = {
-    "tabdo NERDTreeClose" -- Close NERDTree before saving session
-  }
+    "tabdo NERDTreeClose", -- Close NERDTree before saving session
+  },
 
   post_cwd_changed_cmds = {
     function()
       require("lualine").refresh() -- example refreshing the lualine status line _after_ the cwd changes
-    end
-  }
-
-})
+    end,
+  },
+}
 ```
 
 ## 🖥️ Dashboards
@@ -285,10 +285,9 @@ require('auto-session').setup({
 If you use a dashboard, you probably don't want to try and save a session when just the dashboard is open. To avoid that, add your dashboard filetype to the bypass list as follows:
 
 ```lua
-require('auto-session').setup({
-  bypass_save_filetypes = { 'alpha', 'dashboard' } -- or whatever dashboard you use
-})
-
+require("auto-session").setup {
+  bypass_save_filetypes = { "alpha", "dashboard" }, -- or whatever dashboard you use
+}
 ```
 
 ## 🪝 Command Hooks
@@ -311,22 +310,22 @@ Command hooks exist in the format: {hook_name}
 Each hook is a table of vim commands or lua functions (or a mix of both):
 
 ```lua
-require('auto-session').setup {
+require("auto-session").setup {
   -- {hook_name}_cmds = {"{hook_command1}", "{hook_command2}"}
 
   pre_save_cmds = {
-    "tabdo NERDTreeClose" -- Close NERDTree before saving session
+    "tabdo NERDTreeClose", -- Close NERDTree before saving session
   },
 
   post_restore_cmds = {
     "someOtherVimCommand",
     function()
       -- Restore nvim-tree after a session is restored
-      local nvim_tree_api = require('nvim-tree.api')
+      local nvim_tree_api = require "nvim-tree.api"
       nvim_tree_api.tree.open()
       nvim_tree_api.tree.change_root(vim.fn.getcwd())
       nvim_tree_api.tree.reload()
-    end
+    end,
   },
 
   -- Save quickfix list and open it when restoring the session
@@ -334,8 +333,10 @@ require('auto-session').setup {
     function()
       local qflist = vim.fn.getqflist()
       -- return nil to clear any old qflist
-      if #qflist == 0 then return nil end
-      local qfinfo = vim.fn.getqflist({ title = 1 })
+      if #qflist == 0 then
+        return nil
+      end
+      local qfinfo = vim.fn.getqflist { title = 1 }
 
       for _, entry in ipairs(qflist) do
         -- use filename instead of bufnr so it can be reloaded
@@ -343,11 +344,12 @@ require('auto-session').setup {
         entry.bufnr = nil
       end
 
-      local setqflist = 'call setqflist(' .. vim.fn.string(qflist) .. ')'
-      local setqfinfo = 'call setqflist([], "a", ' .. vim.fn.string(qfinfo) .. ')'
-      return { setqflist, setqfinfo, 'copen' }
+      local setqflist = "call setqflist(" .. vim.fn.string(qflist) .. ")"
+      local setqfinfo = 'call setqflist([], "a", ' .. vim.fn.string(qfinfo) .. ")"
+      return { setqflist, setqfinfo, "copen" }
     end,
   },
+}
 ```
 
 ## ➖ Statusline
@@ -357,17 +359,17 @@ You can show the current session name in the statusline by using the function `c
 Here's an example using [Lualine](https://github.com/nvim-lualine/lualine.nvim):
 
 ```lua
-require('lualine').setup{
+require("lualine").setup {
   options = {
-    theme = 'tokyonight',
+    theme = "tokyonight",
   },
   sections = {
     lualine_c = {
       function()
-        return require('auto-session.lib').current_session_name(true)
-      end
-    }
-  }
+        return require("auto-session.lib").current_session_name(true)
+      end,
+    },
+  },
 }
 ```
 
@@ -383,16 +385,16 @@ This feature can come in handy when starting Neovim from a GUI for example.
 :warning: This feature is still experimental and as of right now it interferes with the plugin's ability to auto create new sessions when opening Neovim in a new directory.
 
 ```lua
-require('auto-session').setup {
-    auto_restore_last_session = true,
+require("auto-session").setup {
+  auto_restore_last_session = true,
 }
 ```
 
 A quick workaround for inability to auto create new sessions is to conditionally enable last session.
 
 ```lua
-require('auto-session').setup {
-    auto_restore_last_session = vim.loop.cwd() == vim.loop.os_homedir(),
+require("auto-session").setup {
+  auto_restore_last_session = vim.loop.cwd() == vim.loop.os_homedir(),
 }
 ```
 
@@ -406,13 +408,12 @@ With `auto_create = false`, AutoSession won't create a session automatically. If
 
 ```lua
 
-require('auto-session').setup({
+require("auto-session").setup {
   auto_create = function()
-    local cmd = 'git rev-parse --is-inside-work-tree'
-    return vim.fn.system(cmd) == 'true\n'
+    local cmd = "git rev-parse --is-inside-work-tree"
+    return vim.fn.system(cmd) == "true\n"
   end,
-})
-
+}
 ```
 
 With the above, AutoSession will allow automatically creating a session inside of a git project but won't automatically create a session in any other directory. If you manually save a session in a directory, though, it will then update that session automatically whenever you exit `nvim`.
@@ -422,14 +423,18 @@ With the above, AutoSession will allow automatically creating a session inside o
 By default, when `nvim` is run with a single directory argument, AutoSession will try to restore the session for that directory. If `nvim` is run with multiple directories or any file arguments, AutoSession won't try to restore a session and won't auto-save a session on exit (if enabled). Those behaviors can be changed with these config parameters:
 
 ```lua
+opts = {
   args_allow_single_directory = true, -- boolean Follow normal session save/load logic if launched with a single directory as the only argument
   args_allow_files_auto_save = false, -- boolean|function Allow saving a session even when launched with a file argument (or multiple files/dirs). It does not load any existing session first. While you can just set this to true, you probably want to set it to a function that decides when to save a session when launched with file args. See documentation for more detail
+}
 ```
 
 For `args_allow_single_directory`, if you frequently use `netrw` to look at directories, you might want to add it to `bypass_save_filetypes` if you don't want to create a session for each directory you look at:
 
 ```lua
-      bypass_save_filetypes = { 'netrw' }
+opts = {
+  bypass_save_filetypes = { "netrw" },
+}
 ```
 
 Also, if you use a plugin that handles directory arguments (e.g. file trees/explorers), it may prevent AutoSession from loading or saving sessions when launched with a directory argument. You can avoid that by lazy loading that plugin (e.g. [Oil](https://github.com/rmagatti/auto-session/issues/372#issuecomment-2471077783), [NvimTree](https://github.com/rmagatti/auto-session/issues/393#issuecomment-2474797271)).
@@ -437,7 +442,7 @@ Also, if you use a plugin that handles directory arguments (e.g. file trees/expl
 If `args_allow_files_auto_save` is true, AutoSession won't load any session when `nvim` is launched with file argument(s) but it will save on exit. What's probably more useful is to set `args_allow_files_auto_save` to a function that returns true if a session should be saved and false otherwise. AutoSession will call that function on auto save when run with arguments. Here's one example config where it will save the session if at least two buffers are open after being launched with arguments:
 
 ```lua
-require('auto-session').setup({
+require("auto-session").setup {
   args_allow_files_auto_save = function()
     local supported = 0
 
@@ -446,20 +451,22 @@ require('auto-session').setup({
       -- Check if the buffer is valid and loaded
       if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_is_loaded(buf) then
         local path = vim.api.nvim_buf_get_name(buf)
-        if vim.fn.filereadable(path) ~= 0 then supported = supported + 1 end
+        if vim.fn.filereadable(path) ~= 0 then
+          supported = supported + 1
+        end
       end
     end
 
     -- If we have more 2 or more supported buffers, save the session
     return supported >= 2
   end,
-})
+}
 ```
 
 Another possibility is to only save the session if there are at least two windows with buffers backed by normal files:
 
 ```lua
-require('auto-session').setup({
+require("auto-session").setup {
   args_allow_files_auto_save = function()
     local supported = 0
 
@@ -469,14 +476,16 @@ require('auto-session').setup({
       for _, window in ipairs(windows) do
         local buffer = vim.api.nvim_win_get_buf(window)
         local file_name = vim.api.nvim_buf_get_name(buffer)
-        if vim.fn.filereadable(file_name) ~= 0 then supported = supported + 1 end
+        if vim.fn.filereadable(file_name) ~= 0 then
+          supported = supported + 1
+        end
       end
     end
 
     -- If we have 2 or more windows with supported buffers, save the session
     return supported >= 2
   end,
-})
+}
 ```
 
 ## 🚫 Disabling the plugin
diff --git a/lua/auto-session/config.lua b/lua/auto-session/config.lua
index a9fe4cd7..73e266e0 100644
--- a/lua/auto-session/config.lua
+++ b/lua/auto-session/config.lua
@@ -81,6 +81,7 @@ local defaults = {
   git_auto_restore_on_branch_change = false, -- Should we auto-restore the session when the git branch changes. Requires git_use_branch_name
   lazy_support = true, -- Automatically detect if Lazy.nvim is being used and wait until Lazy is done to make sure session is restored correctly. Does nothing if Lazy isn't being used. Can be disabled if a problem is suspected or for debugging
   bypass_save_filetypes = nil, -- List of filetypes to bypass auto save when the only buffer open is one of the file types listed, useful to ignore dashboards
+  ignore_filetypes_on_save = { "checkhealth" }, -- List of filetypes to close buffers of before saving a session, ignores checkhealth by default
   close_unsupported_windows = true, -- Close windows that aren't backed by normal file before autosaving a session
   args_allow_single_directory = true, -- Follow normal session save/load logic if launched with a single directory as the only argument
   args_allow_files_auto_save = false, -- Allow saving a session even when launched with a file argument (or multiple files/dirs). It does not load any existing session first. While you can just set this to true, you probably want to set it to a function that decides when to save a session when launched with file args. See documentation for more detail
diff --git a/lua/auto-session/init.lua b/lua/auto-session/init.lua
index 1ed9421a..85218987 100644
--- a/lua/auto-session/init.lua
+++ b/lua/auto-session/init.lua
@@ -2,6 +2,8 @@ local Lib = require "auto-session.lib"
 local Config = require "auto-session.config"
 local AutoCmds = require "auto-session.autocmds"
 
+local uv = vim.uv or vim.loop
+
 ----------- Setup ----------
 
 ---@mod auto-session.api API
@@ -567,6 +569,8 @@ function AutoSession.SaveSessionToDir(session_dir, session_name, show_message)
 
   local session_path = session_dir .. escaped_session_name
 
+  Lib.close_ignored_filetypes(Config.ignore_filetypes_on_save)
+
   AutoSession.run_cmds "pre_save"
 
   -- We don't want to save arguments to the session as that can cause issues
@@ -655,7 +659,7 @@ function AutoSession.RestoreSessionFromDir(session_dir, session_name, opts)
 
     Lib.logger.debug("RestoreSessionFromDir renaming legacy session: " .. legacy_escaped_session_name)
     ---@diagnostic disable-next-line: undefined-field
-    if not vim.loop.fs_rename(legacy_session_path, session_path) then
+    if not uv.fs_rename(legacy_session_path, session_path) then
       Lib.logger.debug(
         "RestoreSessionFromDir rename failed!",
         { session_path = session_path, legacy_session_path = legacy_session_path }
@@ -672,7 +676,7 @@ function AutoSession.RestoreSessionFromDir(session_dir, session_name, opts)
     if vim.fn.filereadable(legacy_user_commands_path) == 1 and not Lib.is_session_file(legacy_user_commands_path) then
       if vim.fn.filereadable(user_commands_path) == 0 then
         Lib.logger.debug("RestoreSessionFromDir Renaming legacy user commands" .. legacy_user_commands_path)
-        vim.loop.fs_rename(legacy_user_commands_path, user_commands_path)
+        uv.fs_rename(legacy_user_commands_path, user_commands_path)
       end
     end
   end
diff --git a/lua/auto-session/lib.lua b/lua/auto-session/lib.lua
index bfe244b7..d49405ad 100644
--- a/lua/auto-session/lib.lua
+++ b/lua/auto-session/lib.lua
@@ -827,6 +827,27 @@ function Lib.has_modified_buffers()
   return false
 end
 
+---Close any buffers that have a ft that is in ignored_filetypes
+---@param ignored_filetypes table list of filetypes to close
+function Lib.close_ignored_filetypes(ignored_filetypes)
+  local filetypes_to_ignore = ignored_filetypes or {}
+  if vim.tbl_isempty(filetypes_to_ignore) then
+    return
+  end
+
+  local buffers = vim.api.nvim_list_bufs()
+
+  for _, buf in ipairs(buffers) do
+    if vim.api.nvim_buf_is_loaded(buf) then
+      local buf_ft = vim.bo[buf].filetype
+      if buf_ft and vim.tbl_contains(filetypes_to_ignore, buf_ft) then
+        vim.api.nvim_buf_delete(buf, { force = true })
+        break
+      end
+    end
+  end
+end
+
 ---Snacks (https://github.com/folke/snacks.nvim) debounce function
 ---@generic T
 ---@param fn T


Test patch:
diff --git a/tests/ignore_filetypes_on_save_spec.lua b/tests/ignore_filetypes_on_save_spec.lua
new file mode 100644
index 00000000..273381bf
--- /dev/null
+++ b/tests/ignore_filetypes_on_save_spec.lua
@@ -0,0 +1,68 @@
+---@diagnostic disable: undefined-field
+local TL = require "tests/test_lib"
+
+describe("Ignore filetypes on save", function()
+  local as = require "auto-session"
+
+  as.setup {
+    ignore_filetypes_on_save = { "text" },
+  }
+
+  TL.clearSessionFilesAndBuffers()
+
+  it("closes buffers of ignored filetypes before saving", function()
+    vim.cmd("e " .. TL.test_file) -- this is a text file
+    vim.cmd "e tests/ignore_filetypes_on_save_spec.lua"
+
+    -- generate default session
+    assert.True(as.AutoSaveSession())
+    assert.equals(1, vim.fn.filereadable(TL.default_session_path))
+
+    -- Check that the text file is not in the session
+    assert.False(TL.sessionHasFile(TL.default_session_path, TL.test_file))
+    -- Check that the lua file is in the session
+    assert.True(TL.sessionHasFile(TL.default_session_path, "tests/ignore_filetypes_on_save_spec.lua"))
+  end)
+
+  TL.clearSessionFilesAndBuffers()
+
+  it("does not close buffers of other filetypes", function()
+    vim.cmd("e " .. TL.test_file) -- this is a text file
+    vim.cmd "e tests/ignore_filetypes_on_save_spec.lua"
+
+    as.setup {
+      ignore_filetypes_on_save = { "lua" },
+    }
+
+    -- generate default session
+    assert.True(as.AutoSaveSession())
+    assert.equals(1, vim.fn.filereadable(TL.default_session_path))
+
+    -- Check that the text file is in the session
+    assert.True(TL.sessionHasFile(TL.default_session_path, TL.test_file))
+    -- Check that the lua file is not in the session
+    assert.False(TL.sessionHasFile(TL.default_session_path, "tests/ignore_filetypes_on_save_spec.lua"))
+  end)
+
+  TL.clearSessionFilesAndBuffers()
+
+  it("does not save a checkhealth buffer", function()
+    vim.cmd("e " .. TL.test_file) -- this is a text file
+    vim.cmd "checkhealth auto-session"
+
+    as.setup {
+      close_filetypes_on_save = { "checkhealth" }, -- or empty if ignoring checkhealth is the default as suggested above
+    }
+
+    -- generate default session
+    assert.True(as.AutoSaveSession())
+    assert.equals(1, vim.fn.filereadable(TL.default_session_path))
+
+    as.RestoreSession()
+
+    -- Check that the text file is in the session
+    assert.True(TL.sessionHasFile(TL.default_session_path, TL.test_file))
+    -- Check that the checkhealth file is not in the session
+    assert.False(TL.sessionHasFile(TL.default_session_path, "health://"))
+  end)
+end)