Repository navigation
fix(close_tab): close only the diff with that tab name, never a user buffer - #373
Conversation
…t markers close_tab skipped the diff path for a tab name without both the `✻` and `⧉` markers. It then looked for a buffer by that name, or by a file name taken from it, and deleted that buffer. A user's README.md buffer went away for the name `README.md` or for a marked name that lost its closing marker. A diff opened with a plain tab name stayed open, and its openDiff call never got an answer. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh`_
…buffer Claude Code calls close_tab only with the tab_name it gave openDiff, and the VS Code extension closes only a tab whose label equals that name. close_tab now always hands the name to diff.close_diff_by_tab_name and never deletes a buffer. A name that matches no diff closes nothing. The answer stays TAB_CLOSED in every case. Claude Code 2.1.312 does not read the text (it logs only a failed call), and VS Code answers TAB_CLOSED when no tab matches. PROTOCOL.md and the tools README now say exactly when. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh`_
The model already describes close_tab as a close by name for any tab (ClaudeClosesTab -> CloseByName), and every fact still reads TRUE, so the model and its facts do not change. check.sh passes. The record also confirms four files that changed on main since the last record and cannot reach diff state or an openDiff answer: terminal/native.lua (#364), tools/get_diagnostics.lua and the close_tab log calls (#366), and tools/open_file.lua (#365, #370). _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh`_
|
@codex review |
|
@codex security review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security ReviewSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…emoved The open-diff gotcha still said close_tab matches other names as buffer names. After this PR it closes only the diff with that exact tab name. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh`_
Re-recorded on the merge with main (#373, #367, #372). The facts and the model are unchanged by the merge, and check.sh passes. The only covered file main changed outside the record is selection.lua, which cannot reach diff state. Part of #363 _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh`_
Why
The goal is to clear the open follow-up issues (#362 and #363 now, #350, #326 and #335 earlier), one small reviewed PR per bug. Part of #363. This PR handles items 4 and 5.
Item 5:
close_tabcould delete a buffer the user opened. Atab_namewithout both the✻and⧉markers skipped the diff path.close_tabthen looked up a buffer withvim.fn.bufnr(tab_name), which also accepts part of a name, or by a file name it took fromtab_name, and rannvim_buf_deleteon it. On main, on Neovim 0.8.3 and 0.12.3:README.mdand✻ [Claude Code] README.md (e18e1e)(closing marker missing) deleted the user'sREADME.mdbuffer.notesdeletednotes.txt, andx/app.luadeletedapp.lua.openDiffopened with a plaintab_namesuch asplain-tabstayed open.close_tabtried to delete the modified proposed buffer, failed with-32000, and Claude'sopenDiffcall never got an answer.What the clients do:
close_tabonly with thetab_nameit gaveopenDiff:✻ [Claude Code] <file name> (<6 hex>) ⧉. I read both call sites in the installed CLI bundle. In 2.1.310 and 2.1.311 the call and its error handling look the same.tab_name. It answersTAB_CLOSEDeven when no tab matches.close_tabnow always handstab_nametodiff.close_diff_by_tab_nameand never deletes a buffer. A name that matches no diff closes nothing.Item 4:
close_tabanswersTAB_CLOSEDwhether it closed the diff, kept a diff the user accepted for another client, or found no diff. I kept that answer and documented it exactly. A different answer text has no reader. Claude Code 2.1.312 does not read the text: it awaits the call and logs only a call that fails, so the model never sees it. VS Code gives the same answer for "no match", and a client written against VS Code can compare the text withTAB_CLOSED.What changed
lua/claudecode/tools/close_tab.lua: no marker check and no buffer lookup ornvim_buf_delete. The handler callsdiff.close_diff_by_tab_name(tab_name, client and client.id or nil)and answersTAB_CLOSED.tests/unit/tools/close_tab_spec.lua(new), throughtools.handle_invoke:plain-tabcloses, and itsopenDiffgetsDIFF_REJECTED.README.md,/tmp/project/README.md, a marked name without⧉, and a full marked name with no diff all answerTAB_CLOSEDand leave the user's buffer.tests/unit/tools/tool_log_lines_spec.lua: I removed the two calls that drove the deleted buffer paths.PROTOCOL.mdandlua/claudecode/tools/README.md: whatclose_tabcloses, and the three cases that answerTAB_CLOSED..agents/skills/verify-diff-lifecycle/covered.lock: re-recorded. See Verification item 4.diff.luadoes not change.Scope
Only
close_tab.closeAllDiffTabs,openDiff, and the diff lifecycle do not change. The answer text does not change.Blast Radius
Users whose Claude client sends
close_tabwith a name that matches no open diff. Before, such a name could delete one of their buffers, or fail with-32000on a modified one. Now it changes nothing and answersTAB_CLOSED. The real Claude Code CLI always sends the name of a diff it opened, so for its diffs nothing changes. A client that opens a diff with a plaintab_namenow gets its diff closed and itsopenDiffanswered byclose_tab. Before, that diff stayed open.Verification
Real Neovim, headless (
repro.lua, 0.8.3 and 0.12.3, same result on both):close_tabnameREADME.mdREADME.md✻ [Claude Code] README.md (e18e1e)README.mdnotesnotes.txtx/app.luaapp.luaplain-tab(diff open with that name)-32000, diff stays, noopenDiffanswerTAB_CLOSED, diff closed,openDiffgetsDIFF_REJECTEDverify-claudecode-nvim (agent-tty, real Neovim 0.12.3,
ws-client.mjsas Claude). The user opensnotes.txt,app.luaandREADME.md, then Claude sends the sameclose_tabcalls and twoopenDiff+close_tabpairs.[No Name].close_tab plain-tabanswers-32000, and theopenDiffgets no answer within 5 s. The marked name✻ [Claude Code] app.lua (a1b2c3) ⧉closes its diff withDIFF_REJECTED.openDiffgetsDIFF_REJECTED. 0 active diffs remain, and the client stays alive.main after the
close_tabcalls:this PR after the same calls:
Unit tests:
80ca130adds 2 tests, and both fail on main. One finds the plain-tab diff still open, and the other finds the user's buffer deleted forREADME.md.b60eacbmakes them pass.mise run check(0 warnings),mise run test(812 successes, 0 failures),stylua --check, andprettier --checkpass on the head commit.verify-diff-lifecycle: every fact reads TRUE, and the model passes (89,205 and 5,269 states). The model already describes
close_tabas a close by name for any tab (ClaudeClosesTab->CloseByName), so the model and its facts do not change. On main the check already read STALE for files changed by fix(terminal): adopt a native terminal only while its job has not ended #364, fix(open_file): close the openFile gaps from #362 #365, fix(tools): pass the component first in close_tab and getDiagnostics log calls #366 and fix(open_file): never change the buffer when openFile selects #370 (terminal adoption, thegetDiagnosticsandclose_tablog calls,openFile). None of them reaches diff state or anopenDiffanswer.03ca54are-recordscovered.lock, andcheck.shreads PASS.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:xhigh