Repository navigation
Closed
Labels
Description
Bug Description
The help section :help claudecode.nvim-working-with-diffs mentions that we can both accept and reject changes using :w or :q. Accepting seems to work fine, but rejecting simply closes one of the diff buffers and then proceeds to do absolutely nothing.
To Reproduce
Steps to reproduce the behavior:
- Setup
claudecode.nvimwithsidekick.nvim:return { { "coder/claudecode.nvim", dependencies = { "folke/snacks.nvim" }, event = "User SidekickCliAttach", keys = { { "<leader>aA", "<cmd>ClaudeCodeDiffAccept<cr>", desc = "Accept Diff" }, { "<leader>aR", "<cmd>ClaudeCodeDiffDeny<cr>", desc = "Reject Diff" }, }, opts = { terminal = { provider = "none", }, diff_opts = { layout = "vertical", open_in_new_tab = true, }, }, }, { "folke/sidekick.nvim", dependencies = { "copilot.lua", "snacks.nvim" }, event = { "BufReadPre", "BufNewFile", "VeryLazy" }, keys = { { "<tab>", function() if not require("sidekick").nes_jump_or_apply() then return "<Tab>" end end, expr = true, desc = "Goto/Apply Next Edit Suggestion", }, { "<c-.>", function() require("sidekick.cli").toggle({ filter = { installed = true } }) end, desc = "Sidekick Toggle", mode = { "n", "t", "i", "x" }, }, { "<leader>as", function() require("sidekick.cli").select({ filter = { installed = true } }) end, desc = "Select CLI", }, { "<leader>ad", function() require("sidekick.cli").close() end, desc = "Detach a CLI Session", }, { "<leader>at", function() require("sidekick.cli").send({ msg = "{this}" }) end, mode = { "x", "n" }, desc = "Send This", }, { "<leader>af", function() require("sidekick.cli").send({ msg = "{file}" }) end, desc = "Send File", }, { "<leader>av", function() require("sidekick.cli").send({ msg = "{selection}" }) end, mode = { "x" }, desc = "Send Visual Selection", }, { "<leader>ap", function() require("sidekick.cli").prompt() end, mode = { "n", "x" }, desc = "Sidekick Select Prompt", }, { "<leader>ac", function() require("sidekick.cli").toggle({ name = "claude", focus = true }) end, desc = "Sidekick Toggle Claude Code", }, { "<leader>ag", function() require("sidekick.cli").toggle({ name = "gemini", focus = true }) end, desc = "Sidekick Toggle Gemini", }, { "<leader>ao", function() require("sidekick.cli").toggle({ name = "opencode", focus = true }) end, desc = "Sidekick Toggle OpenCode", }, }, ---@type sidekick.Config opts = { cli = { win = { keys = { buffers = false, files = false, hide_n = false, hide_ctrl_q = false, hide_ctrl_dot = false, hide_ctrl_z = false, prompt = { "<M-p>", "prompt", mode = "t", desc = "Insert prompt or context" }, stopinsert = { "<c-q>", "stopinsert", mode = "t", desc = "Enter normal mode" }, nav_left = vim.env.ZELLIJ and false, nav_down = vim.env.ZELLIJ and false, nav_up = vim.env.ZELLIJ and false, nav_right = vim.env.ZELLIJ and false, }, }, }, nes = { enabled = function(buf) local bufname = vim.api.nvim_buf_get_name(buf) local ignore_buffers = { "%.env.*" } return not vim.iter(ignore_buffers):any(function(pattern) return string.match(bufname, pattern) end) end, }, }, }, }
- Open claude with sidekick.nvim (i.e.
<leader>acon the above configuration) - Trigger a file edit with claude
- When the diff opens, try to reject with
:q - The current diff buffer closes but the tabpage remains open with the remaining buffer and the change is not rejected
Expected Behavior
The tabpage with the diff closes and claude refuses the edit.
Environment
- Neovim version:
0.12.1 - Claude Code CLI version:
2.1.107 - OS:
Linux <redacted> 6.18.22-1-lts #1 SMP PREEMPT_DYNAMIC Sat, 11 Apr 2026 17:16:25 +0000 x86_64 GNU/Linux - Plugin version:
commit 432121f
Activity
ThomasK33 commentedon Jun 8, 2026
Note
This triage report is AI-generated using Claude Code
Summary
This was confirmed as a real bug: rejecting a Claude diff with
:qdoes nothing — the change is never rejected and Claude is never told. The<leader>ad/:ClaudeCodeDiffDenypath works fine. The behaviour is not specific to the reported configuration (open_in_new_tab = true+terminal.provider = "none"): it also reproduces with the defaultopen_in_new_tab = false. It also appears to have never worked, rather than being a recent regression.Reproduction
The issue was reproduced two independent ways: by driving a real Neovim while a WebSocket client played the role of Claude over the MCP socket, and by a headless script that exercises the real diff code path and performs a genuine
:q. Both agree:DIFF_REJECTEDsent to Claude?:q(reported config)pending; tab lingers:ClaudeCodeDiffDenyrejected:q(default config)pendingIn the new-tab case the proposed window closes but the tab stays open showing the original file — matching the report. In the same-tab case the split simply collapses back to the original file with no other feedback, so the silent failure is even easier to miss there.
Root cause
In
lua/claudecode/diff.lua, the proposed ("new") buffer is created as a scratch buffer (vim.api.nvim_create_buf(false, true)), which implicitly setsbufhidden = "hide", and it is left modified. Rejection is wired only to buffer-destruction autocommands —BufDelete/BufUnload/BufWipeout→_resolve_diff_as_rejected. Because the buffer isbufhidden = "hide", running:qmerely hides the still-loaded buffer instead of destroying it, so none of those autocommands fire and the diff is never resolved (the blockingopenDiffcall parks forever).:ClaudeCodeDiffDenyworks only because it calls_resolve_diff_as_rejecteddirectly, bypassing the autocommands.A tempting one-line fix — switching the buffer to
bufhidden = "wipe"— was tested and found not to work: the proposed buffer is modified, so:qthen aborts withE37: No write since last changeand the window never closes at all.Recommended fix
A
WinClosedautocommand (registered alongside the existing reject autocommands, so the standard cleanup tears it down) resolves the diff as rejected when the proposed window is closed — covering:q,:close,<C-w>c, and closing the tab, regardless ofbufhidden. To avoid a premature rejection when the proposed buffer has been split into several windows, the handler rejects only once the buffer is displayed in no window (WinClosedfires before the closing window leaves the layout, so it is excluded from the count). This mirrors the event other Neovim AI-diff plugins use for the same "user closed the proposed window" case.The candidate fix was applied and verified end-to-end:
:w),close_tab/closeAllDiffTabs, client-disconnect cleanup, new-file diffs, and double-resolution are unaffected (cleanup deletes the autocommand before it closes the window, and_resolve_diff_as_rejectedno-ops once the diff is notpending);luacheckis clean and the full test suite passes (523/523).The exact diff and the reproduction tooling are in the collapsible sections below for review.
Documentation note
The plugin does not appear to ship a Vim help file, so the
:help claudecode.nvim-working-with-diffssection referenced in the report does not seem to exist in the repository; the ":qto reject" behaviour is documented in the README instead. Once a fix lands, that documentation will finally match behaviour. It may also be worth stating explicitly that closing the proposed diff window by any means (:q,:close,<C-w>c, closing the tab) counts as a rejection — matching the "close the diff = reject" contract of the official IDE integrations.Reproduction artifacts
A reproduction fixture and two scripts were created during triage and screened for sensitive content before posting. They are not committed; they are included below so the reproduction and the candidate fix can be reviewed independently. The fastest path to see the bug is the headless one-liner, which needs no WebSocket:
nvim --headless -u NONE -l scripts/repro_issue_238.lua # exit 1 = bug reproduced, exit 0 = fixedRecommended fix — diff for lua/claudecode/diff.lua
scripts/repro_issue_238.lua — headless reproduction / regression gate
scripts/repro_issue_238.sh — WebSocket (MCP) driver that plays the role of Claude
fixtures/issue-238/init.lua — minimal fixture config matching the report
Reproduced and verified on Neovim 0.13.0-dev with the current
main(commit8ec3212).fix(diff): reject diffs on window close so :q works
fix(diff): resolve on :q, stay out of foreign diffs, clean up a faile…
docs(scripts): name the #238 repro as the exception to the `-l` command
ci: run the integration tests on Neovim v0.8.0 too (coder#369)
fix(scripts): make the coder#238 repro's run command and version line…