Skip to content

v0.4.0: Unified diffs, send-to-terminal, and a hardened server

Latest

Choose a tag to compare

@github-actions github-actions released this 09 Nov 15:04
112c0c6

This release adds a VS Code-style unified diff layout, a way to script text into the Claude pane, and a :checkhealth integration, alongside a large wave of hardening to the diff lifecycle, selection tracking, openFile, and the WebSocket server. Most of the work went into making existing MCP tool behavior correct and predictable rather than adding surface area.

Highlights

  • New diff and terminal surface: a compact diff_opts.layout = "unified" single-buffer diff, :ClaudeCodeSendText for scripting text into the Claude pane, :ClaudeCodeCloseAllDiffs, and diff/send lifecycle autocmds (ClaudeCodeDiffOpened/DiffClosed/SendComplete).
  • Diffs that respect your windows: diff cleanup is scoped to claudecode's own diffs and never touches diffview.nvim, fugitive, native :diffsplit, or user buffers; diffs reject cleanly on :q, close when the client disconnects, and restore window sizes afterward.
  • Hardened server and selection handling: strict WebSocket framing (UTF-8 validation, fragment reassembly, correct close codes, port-bind retries, unique client ids), CSPRNG auth tokens with 0600 lock files, reliable visual-selection pushes, and openFile that no longer disturbs your mode, focus, or unsaved changes.

Added

  • :ClaudeCodeSendText {text} and require("claudecode.terminal").send_to_terminal(text, opts) send arbitrary text to the open Claude terminal as if typed at the prompt — submitting by default; :ClaudeCodeSendText! inserts without submitting. Multi-line text is sent as one bracketed-paste block. In-editor native/snacks providers only; external/none warn and return false. (#197, #272, @ThomasK33)

  • :checkhealth claudecode verifies Neovim version, setup(), the Claude CLI, terminal provider, WebSocket server, lock file, and live connection state, with actionable advice on each failure. (#275, @ThomasK33)

  • diff_opts.layout = "unified" renders a VS Code-style unified diff in a single buffer with interleaved deleted/added lines — a compact alternative to "vertical"/"horizontal". Requires Neovim >= 0.9.0; customizable via ClaudeCodeInlineDiffAdd/Delete/AddSign/DeleteSign. (#195, @zhao-tong; #295, #297)

    require("claudecode").setup({
      diff_opts = { layout = "unified" }, -- "vertical" (default), "horizontal", or "unified"
    })
  • :ClaudeCodeCloseAllDiffs closes pending Claude diffs at once. A diff accepted with :w closes once the file holds the accepted text; otherwise the accepted text is placed in the buffer as unsaved changes for :w/:e! so edits are never discarded. (#248, #261)

  • Lifecycle autocmds: User ClaudeCodeSendComplete (fired once per file when a send is accepted while connected, with data = { file_path, start_line, end_line, context }, lines 0-indexed) and User ClaudeCodeDiffOpened / ClaudeCodeDiffClosed carrying diff metadata. Enables post-send logic such as focusing a Claude session running outside Neovim. (#228, #265, #270)

  • Terminal width during diffs: diff_opts.auto_resize_terminal (default true) to opt out of plugin-managed terminal width, and terminal.diff_split_width_percentage to set a distinct width while a diff is open. (#270)

  • terminal.auto_insert (default true): set to false to stay in Normal mode when refocusing the Claude terminal and preserve your scroll position instead of jumping to the prompt. (#233)

  • File explorer coverage: <leader>as / :ClaudeCodeTreeAdd now work in snacks.nvim pickers (snacks_picker_list, covering Snacks.explorer() and modal Snacks.picker.files()/grep()) and in netrw (marked files and the file under the cursor). (#192, #269, @ThomasK33; #62, @makotodejima)

Changed

  • Model picker labels are now version-free evergreen aliases (Claude Opus (Latest), Claude Sonnet (Latest), Claude Haiku (Latest)), with new 1M-context variants (opus[1m], sonnet[1m]) and a Default (account recommended) entry, so they never go stale. (#146, #256, @ThomasK33)
  • :checkhealth claudecode and the MCP serverInfo.version report the real plugin version from the VERSION file instead of a hard-coded value. (#437)

Fixed

Diffs and openFile

  • Diff cleanup (closeAllDiffTabs) is now scoped to claudecode's own tracked diffs and no longer wipes out an open diffview.nvim, fugitive, or native :diffsplit review; openFile/openDiff never reuse a window in diff mode or the preview window. (#277, #290, #384, #391)
  • close_tab closes only the diff with that tab name and never deletes a user buffer. (#373)
  • Rejecting a diff with :q / :close / <C-w>c (or closing the tab) resolves it as rejected, :q no longer hangs, and pending diffs auto-close when the opening client disconnects or the integration stops. (#178, #266, #248, #261)
  • openDiff works when the Claude terminal is the only window, creating a split instead of failing with "No suitable editor window found". (#260)
  • Accepted diffs are handed over in the buffer's own line-ending format; empty buffers are wiped when the terminal provider is none; and accepting a new-file diff with render-markdown.nvim installed no longer segfaults. (#223, #392, #224)
  • openFile selects exactly the requested line range or matched text and keeps the user's Visual mode, unsaved changes, focus, and preview window intact — never prompting W11/W12/W13 and never picking the preview window as the main editor window. (#353, #365, #370, #375, #382, #387, #396, #408)
  • Legacy diff_opts (e.g. vertical_split, open_in_current_tab) take effect again after a config merge-order regression. (#142)

Terminal and selection

  • The Snacks provider no longer fails to launch when a model alias contains shell-glob characters (e.g. opus[1m]); the command is passed as an argv list with no shell interpretation. (#256)
  • Closing the native terminal (e.g. :ClaudeCodeClose) stops the Claude job instead of leaving it running. (#336)
  • The Claude terminal adds loopback hosts to no_proxy/NO_PROXY so a configured proxy no longer tunnels Claude's ws://127.0.0.1 IDE connection and times out queued @ mentions. (#70, #268)
  • focus_after_send = true with provider = "none"/"external" emits a one-time warning pointing to User ClaudeCodeSendComplete instead of failing silently. (#228, #265)
  • Fixed the Snacks "climbing cursor" (cursor one row above the prompt after hide/show); on Neovim >= 0.10 floating terminals are hidden rather than closed. (#240, #183, #271)
  • Worked around a Neovim core bug (< 0.12.2) that fragmented large bracketed pastes into the terminal; controlled by terminal.fix_streamed_paste ("auto" by default). (#252)
  • Quickly-made and single-line visual selections are pushed to Claude reliably; selections flush synchronously on visual-mode exit and persist until the cursor moves, including in the external /ide flow and Select mode. (#246, #267, #367, #381, #388)

MCP tools and server

  • getDiagnostics handles plain file paths without a file:// URI scheme, and IDE diagnostics return grouped URI-based payloads with editor-native ranges and severity names. (#163, #274)
  • :ClaudeCodeAdd and openFile handle $ in file paths. (#286)
  • The auth token is generated from a CSPRNG and the lock file is written atomically with mode 0600 in a 0700 directory. (#259)
  • System sleep/wake is detected and keepalive timestamps are reset to prevent false disconnections. (#141)
  • The WebSocket server retries port binding on EADDRINUSE (parallel instances), closes connections on malformed/oversized/binary frames with the correct status and reason, joins fragmented messages, validates UTF-8 per message, sends only valid UTF-8 regardless of buffer-name bytes, and never repeats a client id within a process. (#258, #283, #378, #383, #390, #395, #405, #422, #431)

Breaking Changes

  • Native terminal adoption is now mark-based. The native terminal adopts only buffers it started (marked with b:claudecode_native_terminal), instead of any visible terminal whose name contains claude. A terminal started by an older plugin version (for example after :Lazy reload following an upgrade) or restored from a session file carries no mark and is no longer adopted — the next open starts a second Claude session, and the old terminal keeps running until you close its buffer or quit Claude in it. If you hit a duplicate session right after upgrading, close the old Claude terminal buffer once. (#349, #352, #364)

Compatibility

  • Integration tests run on Neovim v0.8.0, and a new-tab diff no longer fails there (Neovim 0.8 has no statuscolumn). The unified layout requires Neovim >= 0.9.0 and is skipped on 0.8. (#369, #394)

New Contributors

Full Changelog: v0.3.0...v0.4.0