Claude Code + Emacs Integration Guide 2026
The Workflow
Set up Claude Code as an integrated development companion within GNU Emacs. Use vterm for interactive sessions, shell-mode for quick queries, and custom elisp functions for context-aware prompting directly from your buffers.
Expected time: 20 minutes Prerequisites: Emacs 29+, vterm package, Claude Code CLI installed, Node.js 18+
Setup
1. Install Required Emacs Packages
;; In your init.el or .emacs
(use-package vterm
:ensure t
:config
(setq vterm-max-scrollback 100000)
(setq vterm-shell "/bin/zsh"))
(use-package vterm-toggle
:ensure t
:bind (("C-c t" . vterm-toggle)
("C-c T" . vterm-toggle-cd)))
Vterm provides a full terminal emulator inside Emacs, which Claude Code requires for interactive mode.
2. Create Claude Code Elisp Functions
;; ~/.emacs.d/lisp/claude-code.el
(defgroup claude-code nil
"Claude Code integration for Emacs."
:group 'tools)
(defvar claude-code-buffer-name "*claude-code*"
"Name of the Claude Code vterm buffer.")
(defun claude-code-start ()
"Start an interactive Claude Code session in vterm."
(interactive)
(let ((buf (get-buffer claude-code-buffer-name)))
(if (and buf (buffer-live-p buf))
(switch-to-buffer-other-window buf)
(let ((vterm-shell "claude"))
(vterm claude-code-buffer-name)))))
(defun claude-code-send-region (start end)
"Send the selected region to Claude Code with a prompt."
(interactive "r")
(let* ((code (buffer-substring-no-properties start end))
(file-name (buffer-file-name))
(lang (or (file-name-extension file-name) "text"))
(prompt (read-string "Prompt: " "Explain this code: "))
(full-prompt (format "%s\n\n```%s\n%s\n```" prompt lang code)))
(claude-code--send-to-session full-prompt)))
(defun claude-code-explain-function ()
"Explain the function at point using Claude Code."
(interactive)
(let* ((bounds (bounds-of-thing-at-point 'defun))
(code (buffer-substring-no-properties (car bounds) (cdr bounds)))
(prompt (format "Explain this function concisely:\n\n```\n%s\n```" code)))
(claude-code--send-to-session prompt)))
(defun claude-code-print (prompt)
"Send a one-shot query to Claude Code and display result."
(interactive "sPrompt: ")
(let ((output (shell-command-to-string
(format "claude --print %s"
(shell-quote-argument prompt)))))
(with-current-buffer (get-buffer-create "*claude-output*")
(erase-buffer)
(insert output)
(markdown-mode)
(display-buffer (current-buffer)))))
(defun claude-code--send-to-session (text)
"Send TEXT to the running Claude Code vterm session."
(let ((buf (get-buffer claude-code-buffer-name)))
(unless (and buf (buffer-live-p buf))
(claude-code-start)
(setq buf (get-buffer claude-code-buffer-name))
(sleep-for 2))
(with-current-buffer buf
(vterm-send-string text)
(vterm-send-return))
(switch-to-buffer-other-window buf)))
(provide 'claude-code)
3. Add Keybindings
;; In init.el
(add-to-list 'load-path "~/.emacs.d/lisp/")
(require 'claude-code)
(global-set-key (kbd "C-c c c") #'claude-code-start)
(global-set-key (kbd "C-c c r") #'claude-code-send-region)
(global-set-key (kbd "C-c c e") #'claude-code-explain-function)
(global-set-key (kbd "C-c c p") #'claude-code-print)
4. Configure Environment
# Ensure your shell exports the API key
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.zshrc
source ~/.zshrc
5. Verify
# From Emacs: M-x shell-command RET
claude --print "Hello from Emacs"
# Expected output:
# Hello from Emacs! (or similar greeting)
Or press C-c c c to open an interactive Claude Code session in a split window.
Usage Example
Working on a Python project in Emacs with Claude Code:
;; 1. Open your Python file
;; 2. Select a function with C-M-h (mark-defun)
;; 3. Press C-c c r to send the region to Claude Code
;; 4. At the prompt, type: "Add type hints and improve error handling"
Example interaction with a Python function:
# Original function in your buffer
def fetch_user_data(user_id, api_client, cache):
cached = cache.get(f"user:{user_id}")
if cached:
return cached
response = api_client.get(f"/users/{user_id}")
if response.status_code == 200:
data = response.json()
cache.set(f"user:{user_id}", data, ttl=300)
return data
return None
After C-c c r with prompt “Add type hints and improve error handling”:
from typing import Any
from dataclasses import dataclass
@dataclass
class UserData:
id: str
name: str
email: str
class UserFetchError(Exception):
def __init__(self, user_id: str, status_code: int):
self.user_id = user_id
self.status_code = status_code
super().__init__(f"Failed to fetch user {user_id}: HTTP {status_code}")
def fetch_user_data(
user_id: str,
api_client: "HttpClient",
cache: "CacheBackend",
) -> UserData | None:
"""Fetch user data with caching. Raises UserFetchError on API failure."""
cache_key = f"user:{user_id}"
cached: dict[str, Any] | None = cache.get(cache_key)
if cached is not None:
return UserData(**cached)
response = api_client.get(f"/users/{user_id}")
if response.status_code == 404:
return None
if response.status_code != 200:
raise UserFetchError(user_id, response.status_code)
data = response.json()
cache.set(cache_key, data, ttl=300)
return UserData(**data)
Use C-c c p for quick one-shot queries without entering the interactive session:
C-c c p RET "What's the idiomatic way to handle optional chaining in Emacs Lisp?" RET
Common Issues
- Vterm not displaying colors: Add
(setq vterm-term-environment-variable "xterm-256color")to your config. - Claude Code session dies on Emacs frame close: Use
(setq vterm-kill-buffer-on-exit nil)to preserve the buffer. - Shell environment not loaded in vterm: Set
(setq vterm-shell "/bin/zsh -l")to force login shell which sources your profile.
Why This Matters
Emacs users who add Claude Code to their workflow report completing complex refactoring tasks 40% faster by eliminating context switches between terminal and editor.