AI pair programming with Claude Code in Emacs.
example.mp4
- Let Claude Code shine in the terminal
- No agents, MCP, or IDE integration -- these eat up context
- Multi-tool support: Use Claude, Codex, Gemini, or other AI coding tools via configurable tool registry
- Multiple instances: Run multiple sessions of the same tool per workspace (claude, claude-2, etc.)
- Broadcast to all sessions: Use
C-uprefix to send actions to all active sessions - Workspace-aware sessions: Project-based sessions with Doom/Perspective workspace support (see Sessions)
- Session management: Switch between sessions, switch to "other" session, kill specific sessions
- System notifications: OS notifications with sound when awaiting input (see System Notifications)
- Terminal fixes: Use
uto unstick input box and reset buffer issues (see Tips) - Session resume: Resume previous sessions with tool-specific resume flags
- Execute request with context: Send request with file and line/region context
- Fix error at point: Send flycheck error to Claude with context
- Implement comment at point: Extract comment text and ask Claude to implement it
- Add file or current file: Add files with Claude's @ symbol convention
- C-g sends Esc: Old habits die hard
- Option: Swap RET and M-RET: Optionally swap keys (Claude maps RET to submit, M-RET to newline)
- Option: S-RET as newline: May be more natural
- Option: Shell environment loading: Load shell rc files for PATH and environment variables
- Transient interface: Easy-to-use menu system (default:
C-c C-e)
- Install Claude Code CLI
- Install the eat package in Emacs
Add to your packages.el:
(package! claudemacs
:recipe (:host github :repo "cpoile/claudemacs"))Then in your config.el:
(use-package! claudemacs)(use-package claudemacs
:vc (:url "https://github.com/cpoile/claudemacs"))(use-package claudemacs
:vc (:fetcher github :repo "cpoile/claudemacs"))(straight-use-package
'(claudemacs :type git :host github :repo "cpoile/claudemacs"))Clone this repository and add to your Emacs configuration:
;; Add to load path
(add-to-list 'load-path "/path/to/claudemacs")
;; Load the package
(require 'claudemacs)Use your preferred keybinding (I use C-c C-e). I'd recommend adding it to the relevant mode-maps, instead of using a global-set-key, since that will override the very useful C-c C-e keybind in the eat-semi-char-mode-map (see Using Eat Mode section below).
(require 'claudemacs)
(define-key prog-mode-map (kbd "C-c C-e") #'claudemacs-transient-menu)
(define-key emacs-lisp-mode-map (kbd "C-c C-e") #'claudemacs-transient-menu)
(define-key text-mode-map (kbd "C-c C-e") #'claudemacs-transient-menu)
(define-key python-base-mode-map (kbd "C-c C-e") #'claudemacs-transient-menu)
;; Set a big buffer so we can search our history.
(with-eval-after-load 'eat
(setq eat-term-scrollback-size 400000))Other useful tweaks:
;; If you want it to pop up as a new buffer. Otherwise, it will use "other buffer."
;; Personally, I use the default "other buffer" style.
(add-to-list 'display-buffer-alist
'("^\\*claudemacs"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.33)))
;; Turn on autorevert because Claude modifies and saves buffers. Make it a habit to save
;; before asking Claude anything, because it uses the file on disk as its source of truth.
;; (And you don't want to lose edits after it modifies and saves the files.)
(global-auto-revert-mode t)First, set claude config set --global preferredNotifChannel terminal_bell.
For Mac, you need to do some setup to make notifications work.
- Run the built in
Script Editorprogram, start a new script, and rundisplay notification "Test notification" with title "Test Title" sound name "Frog" - Accept the notification permissions. (Or go into System Settings -> Notifications -> Script Editor and allow notifications there.)
Now you should receive System notifications when Claude Code is waiting for input, or when done.
Unfortunately, clicking on the notification doesn't bring you to Emacs. Open to ideas on how to fix that.
For Linux systems using notify-send, notifications will automatically dismiss by default instead of persisting in the system tray. You can control this behavior with:
;; Auto-dismiss notifications (default: t)
(setq claudemacs-notification-auto-dismiss-linux t)
;; Keep notifications in system tray
(setq claudemacs-notification-auto-dismiss-linux nil)
;; Play sound with notifications (requires canberra-gtk-play)
;; Common sound IDs: "message-new-instant", "bell", "dialog-error", "dialog-warning"
(setq claudemacs-notification-sound-linux "message-new-instant")
;; Disable sound
(setq claudemacs-notification-sound-linux "")I have not tested on windows, so would appreciate any help there (PRs welcome).
Claude Code uses many non-standard unicode characters during its thinking animations, and emojis for its summaries. They look nice, but some of them aren't included in a typical font set (even one patched with Nerd Fonts). So you'll need to add fallbacks.
The fallbacks will differ based on your system.
;;
;; font insanity for Claudemacs
;;
(defun my/setup-custom-font-fallbacks-mac ()
(interactive)
"Configure font fallbacks on mac for symbols and emojis.
This will need to be called every time you change your font size,
to load the new symbol and emoji fonts."
(setq use-default-font-for-symbols nil)
;; --- Configure for 'symbol' script ---
;; We add fonts one by one. Since we use 'prepend',
;; the last one added here will be the first one Emacs tries.
;; So, list them in reverse order of your preference.
;; Least preferred among this list for symbols (will be at the end of our preferred list)
(set-fontset-font t 'symbol "Hiragino Sans" nil 'prepend)
(set-fontset-font t 'symbol "STIX Two Math" nil 'prepend)
(set-fontset-font t 'symbol "Zapf Dingbats" nil 'prepend)
(set-fontset-font t 'symbol "Monaco" nil 'prepend)
(set-fontset-font t 'symbol "Menlo" nil 'prepend)
;; Most preferred for symbols -- use your main font here
(set-fontset-font t 'symbol "JetBrainsMono Nerd Font Mono" nil 'prepend)
;; --- Configure for 'emoji' script ---
;; Add fonts one by one, in reverse order of preference.
;; Least preferred among this list for emojis
(set-fontset-font t 'emoji "Hiragino Sans" nil 'prepend)
(set-fontset-font t 'emoji "STIX Two Math" nil 'prepend)
(set-fontset-font t 'emoji "Zapf Dingbats" nil 'prepend)
(set-fontset-font t 'emoji "Monaco" nil 'prepend)
(set-fontset-font t 'emoji "Menlo" nil 'prepend)
;; (set-fontset-font t 'emoji "Noto Emoji" nil 'prepend) ;; If you install Noto Emoji
;; Most preferred for emojis -- use your main font here
(set-fontset-font t 'emoji "JetBrainsMono Nerd Font Mono" nil 'prepend))
;; to test if you have a font family installed:
; (find-font (font-spec :family "Menlo"))
;; Then, add the fonts after your setup is complete:
(add-hook 'emacs-startup-hook
(lambda ()
(progn
(when (string-equal system-type "darwin")
(my/setup-custom-font-fallbacks-mac)))))(defun my/setup-custom-font-fallbacks-linux ()
(interactive)
"Configure font fallbacks on linux for symbols and emojis.
This will need to be called every time you change your font size,
to load the new symbol and emoji fonts."
(setq use-default-font-for-symbols nil)
;; --- Configure for 'symbol' script ---
;; We add fonts one by one. Since we use 'prepend',
;; the last one added here will be the first one Emacs tries.
;; So, list them in reverse order of your preference.
;; Least preferred among this list for symbols (will be at the end of our preferred list)
;; (set-fontset-font t 'symbol "FreeSerif" nil 'prepend)
;; (set-fontset-font t 'symbol "NotoSansSymbols2" nil 'prepend)
;; (set-fontset-font t 'symbol "NotoSansCJKJP" nil 'prepend)
;; (set-fontset-font t 'symbol "unifont" nil 'prepend)
(set-fontset-font t 'symbol "DejaVu Sans Mono" nil 'prepend)
;; Most preferred for symbols -- use your main font here
(set-fontset-font t 'symbol "JetBrainsMono Nerd Font Mono" nil 'prepend)
;; --- Configure for 'emoji' script ---
;; Add fonts one by one, in reverse order of preference.
;; Least preferred among this list for emojis
;; (set-fontset-font t 'emoji "FreeSerif" nil 'prepend)
;; (set-fontset-font t 'emoji "NotoSansSymbols2" nil 'prepend)
;; (set-fontset-font t 'emoji "NotoSansCJKJP" nil 'prepend)
;; (set-fontset-font t 'emoji "unifont" nil 'prepend)
(set-fontset-font t 'emoji "DejaVuSans" nil 'prepend)
;; (set-fontset-font t 'emoji "Noto Emoji" nil 'prepend) ;; If you install Noto Emoji
;; Most preferred for emojis -- use your main font here
(set-fontset-font t 'emoji "JetBrainsMono Nerd Font Mono" nil 'prepend)
)
;; to test if you have a font family installed:
;; (find-font (font-spec :family "DejaVu Sans Mono"))
;; Then, add the fonts after your setup is complete:
(add-hook 'emacs-startup-hook
(lambda ()
(progn
(when (string-equal system-type "gnu/linux")
(my/setup-custom-font-fallbacks-linux)))))
I'm not sure of the built in fonts for Windows, or which ones should be used as fallbacks for Claude Code. PRs welcome.
--- Session Names ---
- The Claudemacs session is based on Doom/Perspective workspace, and the Claude Code's
cwdis the project's git-root (by default -- see below). - Why?
- This allows you to have multiple workspaces in a monorepo, and a separate Claudemacs session per workspace, but each session will be correctly rooted to the project's git root.
- If you don't use workspaces, the decision sequence is: Doom workspace -> Perspective name -> project root dir name
- Supports: Doom Emacs workspaces, perspective.el (vanilla), and fallback to project root
--- Claude Code CWD ---
- You can make Claude Code use your projectile root as it's
cwdby setting:
(setq claudemacs-prefer-projectile-root t)- Why?
- Claude Code is forbidden to auto-edit or auto-read files outside its
cwd. This is annoying if you have the following repo structure:
- Claude Code is forbidden to auto-edit or auto-read files outside its
monorepo/
├── backend/
│ ├── .git/
│ └── api/
│ └── server.py
└── frontend/
├── .git/
└── src/
└── app.tsx
By putting a .projectile file in the parent and using (setq claudemacs-prefer-projectile-root t), Claude Code will be able to read and edit all files, like so:
monorepo/
├── .projectile
├── backend/
│ ├── .git/
│ └── api/
│ └── server.py
└── frontend/
├── .git/
└── src/
└── app.tsx
🎉 NOTE: Since implementing this feature there was a new Claude Code improvement that let's you manually add directories to a project's safe list. You could add them like:
(setq claudemacs-program-switches '("--add-dir ../apps ../libs"))You could also add them to a project's .dir-locals.el and have it customized per project.
Claudemacs provides a transient menu accessible via C-c C-e (or your own keybinding):
Core Commands
s- Switch to session (or select from multiple)S- Start Session submenu (select tool, with switches)o- Switch to other session (second most recent)r- Resume Session submenu (select tool to resume)k- Kill session (select from active sessions)t- Toggle buffer visibility
Start/Resume Submenus (S and r)
These open a submenu where you can select which tool to start/resume:
1- First tool in registry (default: claude)2- Second tool (default: codex)3- Third tool (default: gemini)RET- Start/resume default tool
Switches available in submenus:
-d- Skip permissions on start (--dangerously-skip-permissionsor equivalent)-p- Prompt for project root directory-f- Add custom flag (prompts for input)
Action Commands (use C-u prefix to send to all sessions)
e- Fix error at point (using flycheck if available)x- eXecute request with file context (current line or region)X- eXecute request with no contexti- Implement comment (extracts comment text and asks Claude to implement it)f- Add file reference (@file) to conversationF- Add current file reference to conversationa- Add context (sends file:line or file:line-range without newline)
Quick Responses
y- Send Yes (RET)n- Send No (ESC)
Maintenance
u- Unstick Claude input box (reset buffer tracking)
Additional M-x commands:
M-x claudemacs-setup- Re-run setup (hooks/advice)M-x claudemacs-setup-bell-handler- Re-setup notification handler
Claudemacs provides several customization variables to tailor the experience to your workflow:
Configure which AI coding tools are available:
;; Default registry includes Claude, Codex, and Gemini
(setq claudemacs-tool-registry
'((claude :program "claude" :switches nil)
(codex :program "codex" :switches nil)
(gemini :program "gemini-cli" :switches nil)))
;; Add a custom tool or modify switches
(setq claudemacs-tool-registry
'((claude :program "claude" :switches ("--verbose"))
(codex :program "codex" :switches nil)
(aider :program "aider" :switches ("--no-auto-commits"))))
;; Set the default tool (default: 'claude)
(setq claudemacs-default-tool 'claude);; Fallback executable path if not in tool registry (default: "claude")
(setq claudemacs-program "/usr/local/bin/claude")
;; Fallback switches if not in tool registry (default: nil)
(setq claudemacs-program-switches '("--verbose"));; Whether to switch to Claudemacs buffer when creating new session (default: t)
(setq claudemacs-switch-to-buffer-on-create nil)
;; Whether to switch to Claudemacs buffer when toggling visibility (default: t)
(setq claudemacs-switch-to-buffer-on-toggle nil)
;; Whether to switch to Claudemacs buffer when adding file references (default: nil)
(setq claudemacs-switch-to-buffer-on-file-add t)
;; Whether to switch to Claudemacs buffer when sending error fix requests (default: nil)
(setq claudemacs-switch-to-buffer-on-send-error t)
;; Whether to switch to Claudemacs buffer when adding context (default: t)
(setq claudemacs-switch-to-buffer-on-add-context nil)
;; Whether to prefer projectile root over git root when available (default: nil)
(setq claudemacs-prefer-projectile-root t);; Swap RET and M-RET behavior in Claudemacs buffers (default: nil)
;; When enabled: RET creates newline, M-RET submits
(setq claudemacs-m-return-is-submit t)
;; Enable Shift-Return to create newlines (default: t)
;; Provides alternative to M-RET for creating newlines
(setq claudemacs-shift-return-newline t);; Run Claude through interactive shell to load shell environment (default: nil)
;; When enabled, Claude is invoked through your shell (e.g., zsh -i -c "claude ...")
;; which sources rc files like .zshrc or .bashrc, making shell-configured PATH
;; and environment variables available to Claude.
;; Useful if Claude can't find commands that are in your shell's PATH.
;; NOTE: Changes only apply to new sessions - kill and restart to take effect.
(setq claudemacs-use-shell-env t);; Whether to show system notifications when Claude is awaiting input (default: t)
(setq claudemacs-notify-on-await t)
;; Sound to use for macOS notifications (default: "Submarine")
;; Available sounds: Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse,
;; Ping, Pop, Purr, Sosumi, Submarine, Tink
(setq claudemacs-notification-sound-mac "Ping")
;; Auto-dismiss Linux notifications instead of persisting to system tray (default: t)
(setq claudemacs-notification-auto-dismiss-linux nil)
;; Sound for Linux notifications using canberra-gtk-play (default: "bell")
;; Common sound IDs: "message-new-instant", "bell", "dialog-error", "dialog-warning"
(setq claudemacs-notification-sound-linux "message-new-instant")All variables can also be customized via M-x customize-group RET claudemacs RET.
Claudemacs provides a startup hook that runs after a session has finished initializing. Hook functions execute with the claudemacs buffer as the current buffer.
;; Example: Custom initialization based on project type
(add-hook 'claudemacs-startup-hook
(lambda ()
(when (file-exists-p (expand-file-name "package.json" claudemacs--cwd))
(message "Node.js project detected in %s. Do stuff." claudemacs--cwd))))
Claudemacs creates workspace-aware buffer names that include the tool name:
- With workspace:
*claudemacs:claude:workspace-name* - Without workspace:
*claudemacs:claude:/path/to/project* - Multiple instances:
*claudemacs:claude-2:workspace-name*
The format is *claudemacs:TOOL(-N):SESSION-ID* where:
TOOLis the tool name (claude, codex, gemini, etc.)-Nis the instance number (omitted for first instance)SESSION-IDis the workspace name or project path
Currently supports Doom Emacs workspaces and Perspective mode. Open an issue if you use another workspace package.
When interacting with the eat-mode buffer, you are limited in what you can do in the default semi-char mode.
Press C-c C-e to enter emacs mode. A box cursor will appear, which you can use to move around and select and kill text.
Press C-c C-j to re-enter semi-char mode and continue typing to Claude.
Press C-v to paste an image from the clipboard.
There is a tricky interaction between Eat-mode and Claude Code, probably because Claude Code uses some input libraries that eat has trouble with. It was causing the eat-mode buffer to "scroll-pop" to the top whenever you change the other window's buffer. This is mostly fixed now, but a side effect is sometimes the Claude Clode input box gets stuck halfway up the buffer and won't move.
There are also issues with drawing the input box border after the window resizes, which is expected of terminal programs.
If you see these issues, press u in the Claudemacs transient menu to "unstick" the buffer, and everything should get reset.
Normally, toggling the Claudemacs buffer will close its window, if the window was created for the Claudemacs session. Toggling it again will recreate the window.
But there's an edge case to be aware of: if a window was originally created for Claudemacs, but you've since switched to another workspace and back, that window may have shown other buffers in the meantime. In this case, the window is no longer considered "created just for Claudemacs" and won't automatically close when you toggle. This is due to Emacs' window management - once a window has been reused for other content, it loses its original "dedicated" status.
- Emacs 28.1+
- eat package
- transient (built-in since Emacs 28)
- Claude Code CLI (or other supported tool)
Inspired by:
- Aidermacs by Matthew Zeng
- claude-code.el by Steve Molitor
MIT License. See LICENSE file for details.