From 6ef5f59be6103d3c8d56b99ed0a9aaaf78018b0c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 4 Apr 2026 20:54:27 +0800 Subject: [PATCH] fix(channel): crash on exit after closing v:stderr channel (#38754) Problem: Crash on exit after closing v:stderr channel when piping to stdin. Solution: Reopen stderr as /dev/null or NUL instead of closing it. This also avoids writing to an related file if one is opened after closing v:stderr. (cherry picked from commit e20c4ea9661e3a2d817177620a539b873cae6453) --- src/nvim/channel.c | 8 +++++++- test/functional/core/exit_spec.lua | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 9478ab6d68..aa0d067927 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -167,7 +167,13 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error) chan->stream.err.closed = true; // Don't close on exit, in case late error messages if (!exiting) { - fclose(stderr); + // Don't close the file descriptor, as that may cause later writes to stderr + // to go to an unrelated file. Redirect it to NUL or /dev/null instead. +#ifdef MSWIN + freopen("NUL:", "w", stderr); +#else + freopen("/dev/null", "w", stderr); +#endif } channel_decref(chan); } diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 682649603e..3500cd2919 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -119,12 +119,12 @@ describe(':cquit', function() end) end) -describe('no crash after :quit non-last window during exit', function() +describe('when piping to stdin, no crash during exit', function() before_each(function() n.clear() end) - it('in vim.schedule() callback and when piping to stdin #14379', function() + it('after :quit non-last window in vim.schedule() callback #14379', function() n.fn.system({ n.nvim_prog, '-es', @@ -135,7 +135,7 @@ describe('no crash after :quit non-last window during exit', function() eq(0, n.api.nvim_get_vvar('shell_error')) end) - it('in vim.defer_fn() callback and when piping to stdin #14379', function() + it('after :quit non-last window in vim.defer_fn() callback #14379', function() n.fn.system({ n.nvim_prog, '-es', @@ -145,4 +145,15 @@ describe('no crash after :quit non-last window during exit', function() }, '') eq(0, n.api.nvim_get_vvar('shell_error')) end) + + it('after closing v:stderr channel', function() + n.fn.system({ + n.nvim_prog, + '-es', + '--cmd', + 'call chanclose(v:stderr)', + '+quit', + }, '') + eq(0, n.api.nvim_get_vvar('shell_error')) + end) end)