nvim-tree.lua icon indicating copy to clipboard operation
nvim-tree.lua copied to clipboard

Nvim-tree consumes all my memory

Open razor85 opened this issue 3 months ago • 15 comments

Description

As the title says, nvim-tree is using all my memory in a very particular scenario. I have the following tests inside a C++ application:

#include <filesystem>

int main(int argc, char** argv) {
  std::filesystem::path testFolder("testResources");

  // TODO: Internal FS
  if (std::filesystem::exists(testFolder)) {
    std::error_code ec{};
    std::filesystem::remove_all(testFolder, ec);
  }

  if (!std::filesystem::exists(testFolder)) {
    std::filesystem::create_directories(testFolder);
  }

  return 0;
}

(the test above only removes and recreate a directory if you are not used to C++).

when I run it on a folder that has nvim-tree enabled AND filesystem_watchers also enabled, it keeps allocating memory forever. If I disable filesystem_watchers the bug does not trigger. Upon inspection, the nvim-tree.log has the following lines repeated over and over:

[2025-09-02 23:01:51] [watcher] event_cb 'E:\Dev\bin\testResources' 'E:\Dev\bin\testResources'
[2025-09-02 23:01:51] [watcher] node event scheduled refresh explorer:watch:E:\Dev\bin\testResources:14

Neovim version

NVIM v0.11.4
Build type: Release
LuaJIT 2.1.1741730670

Operating system and version

Windows 10

Windows variant

PowerShell

nvim-tree version

321bc61

Clean room replication

vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1

vim.cmd([[set runtimepath=$VIMRUNTIME]])
vim.cmd([[set packpath=/tmp/nvt-min/site]])
local package_root = "/tmp/nvt-min/site/pack"
local install_path = package_root .. "/packer/start/packer.nvim"
local function load_plugins()
  require("packer").startup({
    {
      "wbthomason/packer.nvim",
      "nvim-tree/nvim-tree.lua",
      "nvim-tree/nvim-web-devicons",
      -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
    },
    config = {
      package_root = package_root,
      compile_path = install_path .. "/plugin/packer_compiled.lua",
      display = { non_interactive = true },
    },
  })
end
if vim.fn.isdirectory(install_path) == 0 then
  print("Installing nvim-tree and dependencies.")
  vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path })
end
load_plugins()
require("packer").sync()
vim.cmd([[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]])
vim.opt.termguicolors = true
vim.opt.cursorline = true

-- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
_G.setup = function()
  require("nvim-tree").setup({})
end

-- UNCOMMENT this block for diagnostics issues, substituting pattern and cmd as appropriate.
-- Requires diagnostics.enable = true in setup.
--[[
vim.api.nvim_create_autocmd("FileType", {
  pattern = "lua",
  callback = function()
    vim.lsp.start {
      name = "my-luals",
      cmd = { "lua-language-server" },
      root_dir = vim.loop.cwd(),
    }
  end,
})
]]

Steps to reproduce

  1. Put >=1 text files on the directory, doesnt matter which size
  2. Build the C++ application above and also put it on the directory
  3. nvim -nu nvt-min.lua
  4. :NvimTreeOpen
  5. Open any file in the directory
  6. Run the application 2 or 3 times
  7. Watch it explode

Expected behavior

No response

Actual behavior

No response

razor85 avatar Sep 03 '25 02:09 razor85

Possibly related #3196 fix #3197

alex-courtis avatar Sep 03 '25 21:09 alex-courtis

Tried the above fix, it does not solve the problem

razor85 avatar Sep 03 '25 21:09 razor85

I've attempted to replicate on a linux ext4 file system. It looks like we are not destroying / recreating the watcher. It appears timing related, with different OS/FS giving different results.

Could you please repeat the test on WSL or another OS?

Either way this will be fixed.

all: rep

rep: rep.cpp

clean:
	rm -f rep rep.o

CXX = clang++
make rep
echo foo > foo
echo bar > bar
echo baz > baz

open tree open foo

run rep several times

no runaway processing

[2025-09-04 08:18:16] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198' nil
[2025-09-04 08:18:16] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:18:16] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198'

[2025-09-04 08:18:32] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:18:32] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:18:32] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:18:32] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/testResources' nil
[2025-09-04 08:18:32] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:18:32] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/testResources'

[2025-09-04 08:18:38] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/testResources' 'testResources'
[2025-09-04 08:18:38] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/testResources:2
[2025-09-04 08:18:38] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/testResources' 'testResources'
[2025-09-04 08:18:38] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/testResources:2
[2025-09-04 08:18:38] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:18:38] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:18:38] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:18:38] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:18:38] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:18:38] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'

[2025-09-04 08:18:54] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:18:54] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:18:54] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:18:54] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:18:54] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'

[2025-09-04 08:19:06] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:19:06] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:19:06] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:19:06] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:19:06] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'

[2025-09-04 08:19:11] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:19:11] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:19:11] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:19:11] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:19:11] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'

alex-courtis avatar Sep 03 '25 22:09 alex-courtis

Putting a sleep in there results in expected logs:

#include <filesystem>
#include <unistd.h>

int main(int argc, char** argv) {
  std::filesystem::path testFolder("testResources");

  // TODO: Internal FS
  if (std::filesystem::exists(testFolder)) {
    std::error_code ec{};
    std::filesystem::remove_all(testFolder, ec);
  }

  sleep(1);

  if (!std::filesystem::exists(testFolder)) {
    std::filesystem::create_directories(testFolder);
  }

  return 0;
}
[2025-09-04 08:36:04] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198' nil
[2025-09-04 08:36:04] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:36:04] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:36:04] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/d' nil
[2025-09-04 08:36:04] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/d'
[2025-09-04 08:36:04] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/d'


[2025-09-04 08:36:11] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:36:11] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:36:11] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:36:11] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/testResources' nil
[2025-09-04 08:36:11] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:36:11] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/testResources'



[2025-09-04 08:36:16] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/testResources' 'testResources'
[2025-09-04 08:36:16] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/testResources:3
[2025-09-04 08:36:16] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/testResources' 'testResources'
[2025-09-04 08:36:16] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/testResources:3
[2025-09-04 08:36:16] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:36:16] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:36:16] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:36:16] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:36:16] [watcher] Watcher:destroy '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:36:16] [watcher] Event:destroy '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:36:17] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'testResources'
[2025-09-04 08:36:17] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-04 08:36:17] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-04 08:36:17] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/testResources' nil
[2025-09-04 08:36:17] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/testResources'
[2025-09-04 08:36:17] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/testResources'

alex-courtis avatar Sep 03 '25 22:09 alex-courtis

Possibility: use a unique identifier for the file/dir instead of the generated UID https://github.com/nvim-tree/nvim-tree.lua/blob/3fc8de198c15ec4e5395f57b70579b3959976960/lua/nvim-tree/explorer/watch.lua#L85

Event could return a unique identifier based on the fs_event userdata from vim.loop.new_fs_event()

The event registry would need to be keyed by the above rather than the filename.

alex-courtis avatar Sep 03 '25 23:09 alex-courtis

Hey, thanks for helping out, I really appreciate it :) I tried to reproduce the bug on WSL and it didn't happen. I then tried to reproduce it on WSL again but using the same ntfs folder as the windows test (didn't happen too). I also tried on Windows again but using another SSD and it happened again. Repeated the test with exFAT and still happens. Its definitely something with Windows and not with the underlying filesystem i'd say

razor85 avatar Sep 04 '25 00:09 razor85

Hey, thanks for helping out, I really appreciate it :) I tried to reproduce the bug on WSL and it didn't happen. I then tried to reproduce it on WSL again but using the same ntfs folder as the windows test (didn't happen too). I also tried on Windows again but using another SSD and it happened again. Repeated the test with exFAT and still happens. Its definitely something with Windows and not with the underlying filesystem i'd say

Many thanks for digging. Windows is known to behave strangely.

I'll make a branch for a fix, however I'm going to need you to test as I don't have access to windows.

alex-courtis avatar Sep 04 '25 02:09 alex-courtis

Sure! Count me in :)

razor85 avatar Sep 04 '25 13:09 razor85

nvim implemented a watcher using similar fs_event_t mechanism, in 0.9, seemingly just for lsp https://github.com/neovim/neovim/pull/22405

~We could migrate to that.~

It look to be internal only, using recursive watchers, using many fs_stat calls.

alex-courtis avatar Sep 08 '25 02:09 alex-courtis

This is quite complex:

  • There is no mechanism in libuv to check the validity of a uv_fs_event_t; is_active etc.
  • An fs_stat at the time of the event should indicate the validity of the file, which could be torn down immediately. This would be very expensive and defeat the purpose of the efficient watcher. We don't want to do anything until after debouncing.
  • We could check at the time of the debounced callback. This would be too late, however we could check some sort of reference like ino however that would likely be very OS dependent
  • uv_handle_t fileno is not useful, usually nil
  • event data in the callback is not useful - it's change and rename only, whatever that means

Yes, this is a problem that can be demonstrated on linux - stale directory watcher - however it's not been a problem, even with generated resources like node modules.

Perhaps we should just resolve the issue for windows powershell; a stat for each event may have to be the tradeoff.

alex-courtis avatar Sep 08 '25 04:09 alex-courtis

@razor85 I'd be really grateful if you could collect some information for me. I'm interested in what windows supplies as the fileno and ino.

Please add the following logs to watcher.lua ~line 77

  local event_cb = vim.schedule_wrap(function(err, filename, event)
    local stat = vim.loop.fs_stat(self.path)
    log.line("watcher", "event_cb '%s' fileno = %s event = %s fs_stat = %s",
      self.path,
      self.fs_event:fileno(),
      vim.inspect(event, { newline = "" }),
      stat and vim.inspect(stat.ino, { newline = "" })
    )

A simple test of: Open nvim-tree

mkdir foo
touch foo/bar
rm foo/bar
rmdir foo
mkdir foo
touch foo/bar

I want to see if the ino changes when recreating the foo directory. For reference, on linux:

[2025-09-08 14:14:14] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:14] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:14] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:14] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:14] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/foo' nil
[2025-09-08 14:14:14] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:14] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113148
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:20] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  change = true} fs_stat = 4113148
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:20] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:20] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:33] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113148
[2025-09-08 14:14:33] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:33] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:33] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = nil
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = nil
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:41] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:41] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:41] [watcher] Watcher:destroy '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:41] [watcher] Event:destroy '/home/alex/src/nvim-tree/r/3198/foo'




[2025-09-08 14:14:45] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:45] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:45] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:45] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:45] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/foo' nil
[2025-09-08 14:14:45] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:45] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113154
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:50] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:4
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  change = true} fs_stat = 4113154
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:50] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:4
[2025-09-08 14:14:50] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'

alex-courtis avatar Sep 08 '25 04:09 alex-courtis

Alternative / workaround:

This case seems very similar to node_modules and yarn, which creates / deletes / recreates a lot. It doesn't fail, however it's a massive resource hog, with many reported issues. We added node_modules to the default watcher ignore config https://github.com/nvim-tree/nvim-tree.lua/pull/2940

Would it be appropriate for you to do the same?

alex-courtis avatar Sep 08 '25 04:09 alex-courtis

@razor85 I'd be really grateful if you could collect some information for me. I'm interested in what windows supplies as the fileno and ino.

Please add the following logs to watcher.lua ~line 77

local event_cb = vim.schedule_wrap(function(err, filename, event) local stat = vim.loop.fs_stat(self.path) log.line("watcher", "event_cb '%s' fileno = %s event = %s fs_stat = %s", self.path, self.fs_event:fileno(), vim.inspect(event, { newline = "" }), stat and vim.inspect(stat.ino, { newline = "" }) )

A simple test of: Open nvim-tree

mkdir foo touch foo/bar rm foo/bar rmdir foo mkdir foo touch foo/bar

I want to see if the ino changes when recreating the foo directory. For reference, on linux:

[2025-09-08 14:14:14] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:14] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:14] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:14] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:14] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/foo' nil
[2025-09-08 14:14:14] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:14] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113148
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:20] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  change = true} fs_stat = 4113148
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:20] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:20] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:33] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113148
[2025-09-08 14:14:33] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:33] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:33] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = nil
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = nil
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:41] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:41] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:41] [watcher] Watcher:destroy '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:41] [watcher] Event:destroy '/home/alex/src/nvim-tree/r/3198/foo'




[2025-09-08 14:14:45] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:45] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:45] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:45] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:45] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/foo' nil
[2025-09-08 14:14:45] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:45] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113154
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:50] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:4
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  change = true} fs_stat = 4113154
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:50] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:4
[2025-09-08 14:14:50] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'

@razor85 I'd be really grateful if you could collect some information for me. I'm interested in what windows supplies as the fileno and ino.

Please add the following logs to watcher.lua ~line 77

local event_cb = vim.schedule_wrap(function(err, filename, event) local stat = vim.loop.fs_stat(self.path) log.line("watcher", "event_cb '%s' fileno = %s event = %s fs_stat = %s", self.path, self.fs_event:fileno(), vim.inspect(event, { newline = "" }), stat and vim.inspect(stat.ino, { newline = "" }) )

A simple test of: Open nvim-tree

mkdir foo touch foo/bar rm foo/bar rmdir foo mkdir foo touch foo/bar

I want to see if the ino changes when recreating the foo directory. For reference, on linux:

[2025-09-08 14:14:14] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:14] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:14] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:14] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:14] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/foo' nil
[2025-09-08 14:14:14] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:14] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113148
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:20] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  change = true} fs_stat = 4113148
[2025-09-08 14:14:20] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:20] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:20] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:33] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113148
[2025-09-08 14:14:33] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:33] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:33] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = nil
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = nil
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:3
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:41] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:41] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:41] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:41] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:41] [watcher] Watcher:destroy '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:41] [watcher] Event:destroy '/home/alex/src/nvim-tree/r/3198/foo'




[2025-09-08 14:14:45] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' fileno = nil event = {  rename = true} fs_stat = 4097728
[2025-09-08 14:14:45] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198' 'foo'
[2025-09-08 14:14:45] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198:1
[2025-09-08 14:14:45] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198'
[2025-09-08 14:14:45] [watcher] Watcher:create '/home/alex/src/nvim-tree/r/3198/foo' nil
[2025-09-08 14:14:45] [watcher] Event:create '/home/alex/src/nvim-tree/r/3198/foo'
[2025-09-08 14:14:45] [watcher] Event:start '/home/alex/src/nvim-tree/r/3198/foo'



[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  rename = true} fs_stat = 4113154
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:50] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:4
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' fileno = nil event = {  change = true} fs_stat = 4113154
[2025-09-08 14:14:50] [watcher] event_cb '/home/alex/src/nvim-tree/r/3198/foo' 'bar'
[2025-09-08 14:14:50] [watcher] node event scheduled refresh explorer:watch:/home/alex/src/nvim-tree/r/3198/foo:4
[2025-09-08 14:14:50] [watcher] node event executing refresh '/home/alex/src/nvim-tree/r/3198/foo'

Sure. I ran the commands up until the last rmdir (Remove-Item on powershell) when nvim gave me an error:

Error executing vim.schedule lua callback: ...ack\packer\start\nvim-tree.lua/lua/nvim-tree/watcher.lua:81: attempt to index field 'fs_event' (a nil value)                                                                                      stack traceback:                                                                                                                ...ack\packer\start\nvim-tree.lua/lua/nvim-tree/watcher.lua:81: in function 'fn'                                        vim/_editor.lua:366: in function <vim/_editor.lua:365> 

The rest of the logs are on https://gist.github.com/razor85/121211ce99814d611af7ff2f5bc8ce76 (gh didn't allow me to embed the big output here)

razor85 avatar Sep 08 '25 19:09 razor85

Alternative / workaround:

This case seems very similar to node_modules and yarn, which creates / deletes / recreates a lot. It doesn't fail, however it's a massive resource hog, with many reported issues. We added node_modules to the default watcher ignore config #2940

Would it be appropriate for you to do the same?

This workaround won't work for me because I have multiple repositories / nested folders with tests and that will make nvim-tree skip a big chunk of them due to a problematic occurrence. Since I have a shared codebase that might also happens on other projects so if the bug is here to stay I'll just disable the file watcher and refresh manually :)

razor85 avatar Sep 08 '25 19:09 razor85

Sure. I ran the commands up until the last rmdir (Remove-Item on powershell) when nvim gave me an error:

That's great - thank you.

It shows us that there is a unique ino, the removed directory will not stat (reappears at one point) and the fs_event watcher is eventually destroyed, after a lot of thrashing.

The solution of an fs_stat for each event is thus viable.

This workaround won't work for me because I have multiple repositories / nested folders with tests and that will make nvim-tree skip a big chunk of them due to a problematic occurrence. Since I have a shared codebase that might also happens on other projects so if the bug is here to stay I'll just disable the file watcher and refresh manually :)

Understood, just thought I'd share.

alex-courtis avatar Sep 09 '25 00:09 alex-courtis