From 1519a34e43df315649ba09d4865d4dd4268a76b4 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:40:24 +0000 Subject: [PATCH] fix(terminal): autocmds leave terminal open to wiped buffer Problem: if buf_free_all autocommands open a terminal, it will remain open after the buffer is freed. Solution: close terminals again later, this time while blocking autocommands. Did consider terminal_open checking stuff like b_locked_split instead, but that's set during BufHidden, etc., which doesn't mean the buffer's being wiped. --- src/nvim/buffer.c | 7 +++++++ test/functional/terminal/channel_spec.lua | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e0fa9bd220..3a9f68744b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -902,6 +902,13 @@ void buf_freeall(buf_T *buf, int flags) } } + // Autocommands may have opened another terminal. Block them this time. + if (buf->terminal) { + block_autocmds(); + buf_close_terminal(buf); + unblock_autocmds(); + } + ml_close(buf, true); // close and delete the memline/memfile buf->b_ml.ml_line_count = 0; // no lines in buffer if ((flags & BFA_KEEP_UNDO) == 0) { diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 418c86b08d..87e4be6123 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -34,6 +34,16 @@ describe('terminal channel is closed and later released if', function() feed('') -- add input to separate two RPC requests -- channel has been released after one main loop iteration eq(chans - 1, eval('len(nvim_list_chans())')) + + command('autocmd BufWipeout * ++once let id2 = nvim_open_term(str2nr(expand("")), {})') + -- channel hasn't been released yet + eq( + "Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete! | call chansend(id2, 'test')]]) + ) + feed('') -- add input to separate two RPC requests + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) it('opened by nvim_open_term(), closed by chanclose(), and deleted by pressing a key', function()