NeovimでClaude Codeの差分を承認する(claudecode.nvim)
Claude CodeをVS Codeで使うと、書き換え案がエディタの差分画面で開き、読んでから承認できます。Neovimでこれと同じことをするのがcoder/claudecode.nvimです。VS Code拡張がClaude Codeと話しているIDE連携の通信を作者が読み解いてLuaで実装したもので、Claude Codeから見るとNeovimが「IDEの1つ」として見えます。2026年10月時点でGitHubの星は3,140あり、後発のsidekick.nvimでClaude Codeを常駐させる方法より多く使われています。
この記事では、Claude Codeの/ideがNeovimを見つける仕組みをlockファイルの中身で確かめ、そのうえで差分の承認・却下・承認前の手直しがファイルにどう効くかを実際に打って見ていきます。AIをエディタのどこに置くかという全体の整理はNeovimでAIを使う4つの形にまとめてあります。
検証環境:macOS上のNVIM v0.12.4、Claude Code 2.1.311、claudecode.nvim 0.3.0(コミットc0deb41、2026-10-24)。tmuxの中でNeovimとClaude Codeを動かしました。Neovim側は$XDG_CONFIG_HOMEなどを一時ディレクトリに向けて手元の設定と切り離しています。HOMEを変えるとClaude Codeの認証が外れるのでHOMEはそのままにしており、lockファイルは本物の~/.claude/ide/に作られます。権限モードは確認が出る「manual mode」に切り替えて試しました。掲載した画面の文字列とlsofの出力は実行結果で、トークンだけ伏せています。
目次(9項目)
claudecode.nvimは何をつなぐのか
Claude Codeには、手元で動いているエディタに接続する仕組みがあります。エディタ側が127.0.0.1のランダムなポートでWebSocketのサーバを立て、そのポートと接続用のトークンを~/.claude/ide/のlockファイルに書いておく。Claude Codeはそのファイルを見て接続し、MCPの形でエディタの機能を呼びます。ファイルを開く、差分を見せる、診断を取る、といった機能です。claudecode.nvimはこのエディタ側をNeovimの中に実装しています。
名前がよく似たgreggh/claude-code.nvimは、作りがまったく違います。こちらはClaude Codeをターミナルウインドウで開き、Claude Codeがディスク上のファイルを書き換えたらバッファを読み直すだけで、Claude Codeとの間に通信路を持ちません。書き換えは承認する前にディスクへ入り、Neovimは後から追いかけて表示します。claudecode.nvimでは書き換え案が先にNeovimへ届き、人が承認するまでディスクのファイルは変わりません。差分を読んでから受け入れたいなら、選ぶのはclaudecode.nvimのほうです。
vim.packで入れて:ClaudeCodeを起動する
READMEの導入例はlazy.nvim向けで、依存にfolke/snacks.nvimが書いてあります。ただしsnacks.nvimは無くても動き、その場合はNeovim標準のターミナルが使われます。Neovim 0.12のvim.packなら、最低限の設定は次のとおりです。
-- ~/.config/nvim/init.lua
vim.g.mapleader = ' '
vim.pack.add({ 'https://github.com/coder/claudecode.nvim' })
require('claudecode').setup({})
vim.keymap.set('n', '<leader>aa', '<cmd>ClaudeCodeDiffAccept<cr>')
vim.keymap.set('n', '<leader>ad', '<cmd>ClaudeCodeDiffDeny<cr>')
vim.keymap.set('v', '<leader>as', '<cmd>ClaudeCodeSend<cr>')
初回の起動でThese plugins will be installed:と確認が出るのでyで答えると、~/.local/share/nvim/site/pack/core/opt/claudecode.nvimに入ります。READMEに載っている<leader>aaや<leader>adはlazy.nvimのkeysに書かれたもので、プラグイン本体はグローバルなキーマップを1つも作りません。ソースを探してもvim.keymap.setの呼び出しはありませんでした。vim.packで入れた場合は上のように自分で書きます。
あとは:ClaudeCodeで右側にターミナルが開き、Claude Codeが起動します。snacks.nvimが無い環境では、開いたときにNative terminal opened. Press Ctrl-\ Ctrl-N to return to Normal mode.と出ました。
/ideがNeovimを見つける仕組み
意外だったのは、lockファイルが:ClaudeCodeを打つ前にできていたことです。setup()の既定値がauto_start = trueなので、Neovimを起動した時点でサーバが立ちます。中身は次のとおりでした(トークンは先頭8文字だけ残しています)。
$ ls ~/.claude/ide/
58073.lock
$ cat ~/.claude/ide/58073.lock
{
"authToken": "b0ada096…",
"workspaceFolders": [
"/private/tmp/…/t/proj"
],
"ideName": "Neovim",
"transport": "ws",
"pid": 53240
}
中身にポート番号の項目はなく、ファイル名の58073がそのままポートです。lsofで見ると、Neovimのプロセスがそのポートを127.0.0.1だけで待ち受けていました。authTokenは32文字で、ファイルの権限は-rw-------です。PROTOCOL.mdによると、Claude Codeは接続時にこのトークンをx-claude-code-ide-authorizationヘッダで送り、一致しなければNeovim側が接続を断ります。ポートが分かるだけでは入れない作りです。workspaceFoldersにはNeovimのカレントディレクトリが入っていました。
nvim 53240 TCP 127.0.0.1:58073 (LISTEN)
Neovimの外の端末で同じディレクトリからclaudeを起動し、/ideを打つと、選択肢にNeovimとNoneが並びました。Neovimを選ぶと自動接続を有効にするかを聞かれ、答えるとConnected to Neovim.と出て、lsofにも接続が現れます。
nvim 53240 TCP 127.0.0.1:58073->127.0.0.1:52874 (ESTABLISHED)
2.1.311 55637 TCP 127.0.0.1:52874->127.0.0.1:58073 (ESTABLISHED)
:ClaudeCodeで中から起動したClaude Codeは、/ideを打たなくても最初からつながっています。プロセスの環境変数を見るとCLAUDE_CODE_SSE_PORT=58073とENABLE_IDE_INTEGRATION=trueが渡されていて、これが接続先を教えています。外のclaudeと中のclaudeは同時に1つのNeovimへつながりました。Neovimを:qa!で閉じると58073.lockは消え、中で動いていたClaude Codeも一緒に終わりました。
書き換え案をdiffで受け取る、承認と却下
次のようなgreet.pyを開いた状態で、ターミナルのClaude Codeに「greet を f-string に書き換えて」と頼みました。
def greet(name):
return "Hello, " + name
def farewell(name):
return "Bye, " + name
Claude Code側にはOpened changes in Neovim ⧉と出て、Do you want to make this edit to greet.py?の選択肢で止まります。Neovim側では編集中の画面が左右の差分表示に変わり、左が元のgreet.py、右が[Claude Code] greet.py (09b4da) ⧉ (proposed) [+]という名前の提案バッファでした(実際の名前は先頭に星形の記号が付きます)。変わっていない行は+-- 2 lines: if __name__ == "__main__":のように折りたたまれます。見た目は:diffsplitと同じNeovim標準の差分モードなので、]cで変更箇所へ飛ぶといったVimの差分マージの操作がそのまま使えます。
この状態で提案バッファの'buftype'はacwriteで、変更ありの印が付いています。acwriteは「:wをプラグインが受け取る」バッファで、claudecode.nvimはBufWriteCmdで書き込みを横取りして承認として扱います。つまり:wと:ClaudeCodeDiffAccept(上の設定なら<leader>aa)が承認で、:ClaudeCodeDiffDeny(<leader>ad)が却下です。
却下を試すため、続けて「farewell も f-string にして」と頼み、差分が開いたところで提案バッファにカーソルを移して<leader>adを押しました。差分のウインドウが閉じ、greet.pyの5〜6行目は元の"Bye, " + nameのままです。Claude Code側の表示はこうなりました(行頭の丸い記号は省いています)。
Update(greet.py)
⎿ User rejected update to greet.py
Claude Codeは却下を受けて別の書き方を試したりはせず、そのまま入力待ちに戻りました。理由を添えたいときは、却下したあとにプロンプトで伝えることになります。
承認する前にdiffを手で直すとどうなるか
最初の依頼のほうでは、承認する前に提案バッファを手で直しました。右側の2行目を:2s/Hello/Hi/で書き換えてから:wしています。この時点でディスクのgreet.pyはまだ"Hello, " + nameのままで、提案はファイルに入っていません。
:wのあとのファイルはこうなりました。
def greet(name):
return f"Hi, {name}"
保存されたのはClaudeの案ではなく、手で直したあとの提案バッファの中身です。面白いのはClaude Code側の記録で、差分は+ return f"Hi, {name}"と手直し後の内容で表示され、返事にも「承認するときに文言が手直しされたので、greet.py:2 は return f"Hi, {name}" になっています」と書かれていました。手を入れた結果がClaude Codeの文脈にも戻っているので、続けて頼む作業は手直し後のファイルを前提に進みます。案のうち一部だけ受け入れたいときは、提案バッファで要らない行をuで戻すなり消すなりしてから:wすれば済みます。
選択範囲やファイルをClaudeに渡す
greet.pyの5〜6行目をビジュアルモードで選んで:'<,'>ClaudeCodeSendを実行すると、外の端末のClaude Codeのプロンプトに次の行が入りました。
[⧉ 2 lines selected] @greet.py#L5-6
@greet.py#L5-6が@メンションで、行番号はNeovimの表示と同じ1始まりです。前の[⧉ 2 lines selected]は、IDE連携がNeovimの選択状態を常に送っていることを示す表示です。ファイルごと渡すなら:ClaudeCodeAdd %を使います。
ひとつ注意があり、:ClaudeCodeSendは送ったあとにNeovimの中のClaude Codeのターミナルを表示しようとします。外のclaudeだけを使っていて中のターミナルを開いていなかったので、このとき2つ目のClaude Codeが中で起動しました。ソースを読むと、送信に成功したあとterminal.ensure_visibleでターミナルを表示する作りでした。Claude Codeを常にNeovimの外で動かすなら、READMEにあるterminal = { provider = 'none' }を設定しておくのが筋です(この設定そのものは試していません)。
sidekick.nvim・claude-code.nvimとの使い分け
3つとも:terminalの中でClaude Codeを動かせますが、書き換えの受け取り方が違います。
| プラグイン | 書き換えの受け取り方 | 承認の前に止まるか | ほかに使えるCLI |
|---|---|---|---|
| claudecode.nvim | IDE連携で案を受け取り、diffで開く | 止まる | Claude Codeだけ |
| sidekick.nvim | ディスクの変更を読み直す | Claude Code側の確認だけ | CodexやGeminiなど多数 |
| claude-code.nvim | ディスクの変更を読み直す | Claude Code側の確認だけ | Claude Codeだけ |
Claude Codeしか使わず、書き換えを1件ずつ読みたいならclaudecode.nvimが合います。CodexやGeminiのCLIも切り替えて使うならsidekick.nvimです。両方入れること自体はできますが、sidekick.nvimの記事で使った開閉のキーとclaudecode.nvimのREADMEの承認キーがどちらも<leader>aaなので、そのまま写すとぶつかります。greggh/claude-code.nvimは最後のpushが2026年2月で、さらにclaudecode.nvimと同じ:ClaudeCodeというコマンドを定義します(両方のソースで確かめました)。並べて入れると名前を取り合うので、移るなら片方を外します。プラグインを入れずに:terminalや別の端末で動かす方法はプラグイン無しでVimからClaude Codeを使う記事で扱っています。
最初に依頼を送ったとき、差分が開かずにそのまま書き換えられるのではと身構えていました。手元のClaude Codeの権限モードが「auto mode」になっていたからで、試す前にShift-Tabで「manual mode」まで回しています。もうひとつ失敗したのは、承認のあとでターミナルへ戻ろうと:ClaudeCodeFocusを打ってから依頼文を打ち込んだときです。フォーカスがgreet.pyのバッファに残っていて、日本語の依頼文がノーマルモードのコマンドとして流れ込み、farewellがferewellに化けていました。:e!で戻して:ClaudeCodeから開き直しましたが、文字を打つ前に右下のモード表示が-- TERMINAL --になっているかを見る癖がつきました。
まとめ
claudecode.nvimは、Neovimの起動時に~/.claude/ide/<port>.lockを書いてWebSocketで待ち受け、Claude Codeの/ideからIDEとして見つけてもらう仕組みです。:ClaudeCodeで中から起動すれば環境変数で最初からつながり、外の端末からなら/ideで選びます。
書き換え案は左右の差分で開き、:wか<leader>aaで承認、<leader>adで却下です。承認する前に提案バッファを直せば直した内容が保存され、その結果はClaude Code側にも伝わります。キーマップはプラグインが作らないので、vim.packで入れたときは自分で書いておきます。