From fb686bd8b57c7a10affbca7a781c99c32455df78 Mon Sep 17 00:00:00 2001 From: Xavier Ruiz Date: Wed, 7 Jan 2026 17:33:43 -0500 Subject: [PATCH 1/2] test: add case for invalid buffer in tracking causing E5108 When a buffer is deleted externally, a race condition can leave stale entries in tabscope's internal tracking. If `remove_tab_buffer` picks the invalid buffer to show, it causes E5108: Invalid buffer id. The test simulates this by wiping a buffer and re-adding its ID to tracking, reproducing the race condition reliably. --- tests/main.bats | 4 ++++ tests/test-cases.lua | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/tests/main.bats b/tests/main.bats index 3ff159e..a947150 100644 --- a/tests/main.bats +++ b/tests/main.bats @@ -91,3 +91,7 @@ EOF @test "remove local tab buffer that was last for tab" { run_case } + +@test "remove tab buffer when tracked buffer was deleted externally" { + run_case +} diff --git a/tests/test-cases.lua b/tests/test-cases.lua index 395b2eb..fa8b5db 100644 --- a/tests/test-cases.lua +++ b/tests/test-cases.lua @@ -214,4 +214,36 @@ M.remove_local_tab_buffer_that_was_last_for_tab = function() helpers.assert_listed_buffers({ "first", "second" }) end +M.remove_tab_buffer_when_tracked_buffer_was_deleted_externally = function() + local tabscope = require("tabscope") + tabscope.setup() + + -- Create buffers and visit them + local buf1 = vim.api.nvim_create_buf(true, false) + local buf2 = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf1, "first") + vim.api.nvim_buf_set_name(buf2, "second") + vim.api.nvim_set_current_buf(buf1) + vim.api.nvim_set_current_buf(buf2) + + -- Wipe buf1 + vim.cmd("bwipeout! " .. buf1) + assert(not vim.api.nvim_buf_is_valid(buf1), "buf1 should be invalid after wipeout") + + -- Simulate race condition: tracking contains ONLY buf2 and the invalid buf1 + -- Use table replacement to ensure exact state, with invalid buffer ID first + local current_tab = vim.api.nvim_get_current_tabpage() + tabscope.tab_buffers._buffers_by_tab[current_tab] = {} + tabscope.tab_buffers._buffers_by_tab[current_tab][buf1] = true -- invalid, added first + tabscope.tab_buffers._buffers_by_tab[current_tab][buf2] = true -- valid, current + + -- Close buf2 - tabscope must try to show buf1 (the only other tracked buffer) + -- Without the fix: "Invalid buffer id: X" + tabscope.remove_tab_buffer() + + -- Should succeed without error + local listed = helpers.get_listed_buffers() + assert(#listed >= 1, "should have at least one buffer") +end + return M From 23250a3193bed98601d786d81d9a51abd8d529d0 Mon Sep 17 00:00:00 2001 From: Xavier Ruiz Date: Wed, 7 Jan 2026 18:16:42 -0500 Subject: [PATCH 2/2] fix: validate buffer before showing in window Check `nvim_buf_is_valid` before using a tracked buffer in `show_another_buffer_for_window`. This prevents E5108 errors when the internal tracking contains stale buffer IDs due to race conditions with external buffer deletion. --- lua/tabscope/buffer-managers/tab.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/tabscope/buffer-managers/tab.lua b/lua/tabscope/buffer-managers/tab.lua index 0afd556..cbc58be 100644 --- a/lua/tabscope/buffer-managers/tab.lua +++ b/lua/tabscope/buffer-managers/tab.lua @@ -141,7 +141,7 @@ local function new(tracked_buffers) local show_another_buffer_for_window = function(tab, win) local another_buffer_to_show = -1 for buffer, _ in pairs(m._buffers_by_tab[tab]) do - if buffer ~= id then + if buffer ~= id and vim.api.nvim_buf_is_valid(buffer) then another_buffer_to_show = buffer break end