AI Pair Programming with Claude Code/Codex in Emacs
Emacs Lisp
179
105 commits
updated Sep 8, 2026
AI pair programming with Claude Code in Emacs.
https://github.com/user-attachments/assets/a7a8348d-471c-4eec-85aa-946c3ef9d364
C-u prefix to send actions to all active sessionsu to unstick input box and reset buffer issues (see Tips)C-c C-e)Claudemacs only loads the selected backend. You do not need both packages, but the selected backend must be installed and loadable before starting a session.
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 a global
binding can override useful keybindings in the selected terminal's mode map
(see Using the terminal backend 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.
;; This option applies when using the Eat backend.
(with-eval-after-load 'eat
(setq eat-term-scrollback-size 400000))
Claudemacs presents the same session and action commands regardless of the
terminal implementation. Ghostel is selected automatically when its package
is available on load-path; otherwise Claudemacs falls back to Eat. To choose
explicitly, set the backend before creating a session:
;; Force Eat:
(setq claudemacs-terminal-backend 'eat)
;; Force Ghostel (requires the Ghostel package and native module):
(setq claudemacs-terminal-backend 'ghostel)
;; Ghostel Claudemacs sessions confirm before killing a live process (default: t)
;; Other values are `auto' (only while a command is running) and nil (never).
(setq claudemacs-ghostel-query-before-killing t)
;; Increase this if programmatically sent prompts occasionally fail to submit.
(setq claudemacs-ghostel-submit-delay 0.15)
The selector is checked when a session starts and affects new sessions only. Kill and restart existing sessions after changing it. Claudemacs does not automatically fall back to another backend: an invalid, unavailable, or unloadable selection reports an error so the configuration can be corrected.
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.
Script Editor program, start a new script, and run display notification "Test notification" with title "Test Title" sound name "Frog"Now you should receive System notifications when Claude Code is waiting for input, or when done.
For Codex sessions, Claudemacs automatically configures Codex's TUI notification
path to emit a terminal BEL and to notify even while the session is focused.
That lets the selected terminal backend invoke the same Claudemacs system
notification handler. This is separate from Codex's top-level notify hook,
which runs an external command.
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 "")
Windows requires a registered application identity before an unpackaged app can send Notification Center toasts. Claudemacs installs this per-user identity automatically the first time it needs to send a notification; no manual setup is required.
If the identity ever needs to be repaired or reinstalled, run:
M-x claudemacs-setup-windows-notifications
Windows notifications expire after five seconds by default. To change that:
(setq claudemacs-notification-timeout-windows 8)
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 ---
cwd is the project's git-root (by default -- see below).--- Claude Code CWD ---
cwd by setting:(setq claudemacs-prefer-projectile-root t)
cwd. This is annoying if you have the following repo structure: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)l - List live sessionsk - Kill session (select from active sessions)t - Toggle buffer visibilityStart/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 toolSwitches available in submenus:
-d - Skip permissions on start (--dangerously-skip-permissions or equivalent)-p - Prompt for project root directory-f - Add custom command-line arguments (prompts for input)-u - In the resume submenu, resume a Codex session with a UUID (prompts for the UUID)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)When x or X is used from an active Ediff buffer, set claudemacs-open-new-frame-for-ediff-requests to non-nil to preserve the Ediff window layout. Claudemacs shows the session in another frame; if the session is already displayed in a window, that window is reused. The default is nil, so requests otherwise retain the normal behavior.
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 handlerRun M-x claudemacs-session-list (or press l in the Claudemacs transient) to view currently live Claudemacs buffers in one table. Each row represents a running terminal-backed session and shows three columns: workspace, tool instance, and project directory. Session IDs remain internal row keys for visiting the live session.
Press RET to visit the selected live session and g to refresh the list. Sessions whose terminal process has exited disappear on refresh; this command does not display CLI history or historical rows. If Claudemacs cannot determine a session ID safely, it keeps that row's identity as unknown rather than guessing from recency or the project directory. New or forked Codex sessions remain unknown internally; explicit Codex resumes retain the selected authoritative ID.
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
:model-types (("opus-max" :model "opus" :effort "max")
("sonnet-high" :model "sonnet" :effort "high")))
(codex :program "codex" :switches nil
:model-types (("luna-max" :model "gpt-5.6-luna" :effort "max")
("sol-high" :model "gpt-5.6-sol" :effort "high")))
(gemini :program "gemini-cli" :switches nil)))
;; Add a custom tool or modify switches
(setq claudemacs-tool-registry
'((claude :program "claude" :switches ("--verbose")
:model-types (("opus-max" :model "opus" :effort "max")
("sonnet-high" :model "sonnet" :effort "high")))
(codex :program "codex" :switches nil
:model-types (("luna-max" :model "gpt-5.6-luna" :effort "max")
("sol-high" :model "gpt-5.6-sol" :effort "high")))
(aider :program "aider" :switches ("--no-auto-commits"))))
;; Set the default tool (default: 'claude)
(setq claudemacs-default-tool 'claude)
The configured model is shown beside each tool in the start-session menu by
default, along with the m (Toggle model type) menu item. Disable it with:
(setq claudemacs-show-model-in-menu nil)
When enabled, Claudemacs reads Codex's default model and
model_reasoning_effort from ~/.codex/config.toml. It reads Claude Code's
optional model and effortLevel from ~/.claude/settings.json, falling back
to the current sonnet alias when no model is configured. The model text is
shown in comment-face. Press m in the start menu to cycle the configured
model types from each tool's configuration; the selection lasts only for the
current menu invocation and applies only to newly started sessions.
Model types are (NAME PLIST) entries in each tool's registry plist. The
standard :model and :effort keys are translated for Claude Code and Codex;
use :switches for a tool-specific command-line form. For example, this is
a complete custom setting suitable for an init.el:
(setq claudemacs-tool-registry
'((claude :program "claude" :switches nil
:model-types (("careful" :model "opus" :effort "max")
("quick" :model "sonnet" :effort "medium")))
(codex :program "codex" :switches nil
:model-types (("local-fast"
:switches ("--model" "gpt-5.6-sol"
"--config" "model_reasoning_effort=\"high\""))
("local-deep"
:switches ("--model" "gpt-5.6-luna"
"--config" "model_reasoning_effort=\"max\"")))))
;; 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)
;; Show x/X request sessions in a separate frame when invoked from Ediff (default: nil)
(setq claudemacs-open-new-frame-for-ediff-requests t)
;; 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)
Customize environment variables passed to Claude processes:
;; Default enables 24-bit truecolor for Claude Code's syntax highlighting
(setq claudemacs-process-environment
'("TERM=xterm-256color" "COLORTERM=truecolor"))
;; Add additional environment variables as needed
(add-to-list 'claudemacs-process-environment "CLAUDE_CODE_SYNTAX_HIGHLIGHT=off")
;; Whether to show system notifications when Claude is awaiting input (default: t)
(setq claudemacs-notify-on-await t)
;; Codex notification switches are enabled by default so notifications reach
;; the selected terminal backend.
;; Set to nil to use Codex's own notification method and focus condition.
;; (setq claudemacs-codex-notification-switches nil)
;; 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:
*claudemacs:claude:workspace-name**claudemacs:claude:/path/to/project**claudemacs:claude-2:workspace-name*The format is *claudemacs:TOOL(-N):SESSION-ID* where:
TOOL is the tool name (claude, codex, gemini, etc.)-N is the instance number (omitted for first instance)SESSION-ID is the workspace name or project pathCurrently supports Doom Emacs workspaces and Perspective mode. Open an issue if you use another workspace package.
The terminal backend provides a semi-character mode for typing directly to the AI tool and an Emacs mode for editing terminal contents. The keybindings below apply to both Eat and Ghostel.
When interacting with the terminal 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 in semi-char mode to send it to Claude Code, which uses it to paste an image from the clipboard.
Useful helper to copy the current file path with line number (or range) for pasting into Claude:
(defun copy-file-path-with-line ()
"Copy the current file path with line number (or range) to clipboard.
If a region is active, uses the range of lines."
(interactive)
(let* ((file-path (buffer-file-name))
(line-start (line-number-at-pos (if (use-region-p) (region-beginning) (point))))
(line-end (when (use-region-p) (line-number-at-pos (region-end))))
(result (if line-end
(format "%s:%d-%d" file-path line-start line-end)
(format "%s:%d" file-path line-start))))
(if file-path
(progn
(kill-new result)
(message "Copied: %s" result))
(message "Buffer is not visiting a file"))))
Bind it to a key and use it to quickly copy file references to paste into Claude.
Claude Code uses some keybindings that conflict with Emacs defaults. For example, Ctrl-g is used by Claude Code to edit your plan in your editor, but in Emacs C-g is the universal quit key (which claudemacs maps to send ESC to the terminal). In semi-char mode, both Eat and Ghostel provide C-q as a quoted-input escape hatch:
Use it to send the actual Ctrl-g character (or any other control character) to the terminal:
C-q C-g - Send Ctrl-g to Claude Code (e.g., to edit your plan)C-q C-c - Send Ctrl-c to Claude CodeC-q <any-key> - Send that key literally to the terminalThere can be a tricky interaction between a terminal emulator and Claude Code, because Claude Code uses input libraries that depend on terminal behavior. In Eat-mode this can cause the 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 Code 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. The same command is
available for both supported backends.
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.
Inspired by:
MIT License. See LICENSE file for details.
Emacs Lisp
97.7%
PowerShell
1.2%
Makefile
1.2%
AI Pair Programming with Claude Code/Codex in Emacs
Emacs Lisp
179
105 commits
updated Sep 8, 2026
AI pair programming with Claude Code in Emacs.
https://github.com/user-attachments/assets/a7a8348d-471c-4eec-85aa-946c3ef9d364
C-u prefix to send actions to all active sessionsu to unstick input box and reset buffer issues (see Tips)C-c C-e)Claudemacs only loads the selected backend. You do not need both packages, but the selected backend must be installed and loadable before starting a session.
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 a global
binding can override useful keybindings in the selected terminal's mode map
(see Using the terminal backend 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.
;; This option applies when using the Eat backend.
(with-eval-after-load 'eat
(setq eat-term-scrollback-size 400000))
Claudemacs presents the same session and action commands regardless of the
terminal implementation. Ghostel is selected automatically when its package
is available on load-path; otherwise Claudemacs falls back to Eat. To choose
explicitly, set the backend before creating a session:
;; Force Eat:
(setq claudemacs-terminal-backend 'eat)
;; Force Ghostel (requires the Ghostel package and native module):
(setq claudemacs-terminal-backend 'ghostel)
;; Ghostel Claudemacs sessions confirm before killing a live process (default: t)
;; Other values are `auto' (only while a command is running) and nil (never).
(setq claudemacs-ghostel-query-before-killing t)
;; Increase this if programmatically sent prompts occasionally fail to submit.
(setq claudemacs-ghostel-submit-delay 0.15)
The selector is checked when a session starts and affects new sessions only. Kill and restart existing sessions after changing it. Claudemacs does not automatically fall back to another backend: an invalid, unavailable, or unloadable selection reports an error so the configuration can be corrected.
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.
Script Editor program, start a new script, and run display notification "Test notification" with title "Test Title" sound name "Frog"Now you should receive System notifications when Claude Code is waiting for input, or when done.
For Codex sessions, Claudemacs automatically configures Codex's TUI notification
path to emit a terminal BEL and to notify even while the session is focused.
That lets the selected terminal backend invoke the same Claudemacs system
notification handler. This is separate from Codex's top-level notify hook,
which runs an external command.
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 "")
Windows requires a registered application identity before an unpackaged app can send Notification Center toasts. Claudemacs installs this per-user identity automatically the first time it needs to send a notification; no manual setup is required.
If the identity ever needs to be repaired or reinstalled, run:
M-x claudemacs-setup-windows-notifications
Windows notifications expire after five seconds by default. To change that:
(setq claudemacs-notification-timeout-windows 8)
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 ---
cwd is the project's git-root (by default -- see below).--- Claude Code CWD ---
cwd by setting:(setq claudemacs-prefer-projectile-root t)
cwd. This is annoying if you have the following repo structure: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)l - List live sessionsk - Kill session (select from active sessions)t - Toggle buffer visibilityStart/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 toolSwitches available in submenus:
-d - Skip permissions on start (--dangerously-skip-permissions or equivalent)-p - Prompt for project root directory-f - Add custom command-line arguments (prompts for input)-u - In the resume submenu, resume a Codex session with a UUID (prompts for the UUID)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)When x or X is used from an active Ediff buffer, set claudemacs-open-new-frame-for-ediff-requests to non-nil to preserve the Ediff window layout. Claudemacs shows the session in another frame; if the session is already displayed in a window, that window is reused. The default is nil, so requests otherwise retain the normal behavior.
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 handlerRun M-x claudemacs-session-list (or press l in the Claudemacs transient) to view currently live Claudemacs buffers in one table. Each row represents a running terminal-backed session and shows three columns: workspace, tool instance, and project directory. Session IDs remain internal row keys for visiting the live session.
Press RET to visit the selected live session and g to refresh the list. Sessions whose terminal process has exited disappear on refresh; this command does not display CLI history or historical rows. If Claudemacs cannot determine a session ID safely, it keeps that row's identity as unknown rather than guessing from recency or the project directory. New or forked Codex sessions remain unknown internally; explicit Codex resumes retain the selected authoritative ID.
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
:model-types (("opus-max" :model "opus" :effort "max")
("sonnet-high" :model "sonnet" :effort "high")))
(codex :program "codex" :switches nil
:model-types (("luna-max" :model "gpt-5.6-luna" :effort "max")
("sol-high" :model "gpt-5.6-sol" :effort "high")))
(gemini :program "gemini-cli" :switches nil)))
;; Add a custom tool or modify switches
(setq claudemacs-tool-registry
'((claude :program "claude" :switches ("--verbose")
:model-types (("opus-max" :model "opus" :effort "max")
("sonnet-high" :model "sonnet" :effort "high")))
(codex :program "codex" :switches nil
:model-types (("luna-max" :model "gpt-5.6-luna" :effort "max")
("sol-high" :model "gpt-5.6-sol" :effort "high")))
(aider :program "aider" :switches ("--no-auto-commits"))))
;; Set the default tool (default: 'claude)
(setq claudemacs-default-tool 'claude)
The configured model is shown beside each tool in the start-session menu by
default, along with the m (Toggle model type) menu item. Disable it with:
(setq claudemacs-show-model-in-menu nil)
When enabled, Claudemacs reads Codex's default model and
model_reasoning_effort from ~/.codex/config.toml. It reads Claude Code's
optional model and effortLevel from ~/.claude/settings.json, falling back
to the current sonnet alias when no model is configured. The model text is
shown in comment-face. Press m in the start menu to cycle the configured
model types from each tool's configuration; the selection lasts only for the
current menu invocation and applies only to newly started sessions.
Model types are (NAME PLIST) entries in each tool's registry plist. The
standard :model and :effort keys are translated for Claude Code and Codex;
use :switches for a tool-specific command-line form. For example, this is
a complete custom setting suitable for an init.el:
(setq claudemacs-tool-registry
'((claude :program "claude" :switches nil
:model-types (("careful" :model "opus" :effort "max")
("quick" :model "sonnet" :effort "medium")))
(codex :program "codex" :switches nil
:model-types (("local-fast"
:switches ("--model" "gpt-5.6-sol"
"--config" "model_reasoning_effort=\"high\""))
("local-deep"
:switches ("--model" "gpt-5.6-luna"
"--config" "model_reasoning_effort=\"max\"")))))
;; 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)
;; Show x/X request sessions in a separate frame when invoked from Ediff (default: nil)
(setq claudemacs-open-new-frame-for-ediff-requests t)
;; 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)
Customize environment variables passed to Claude processes:
;; Default enables 24-bit truecolor for Claude Code's syntax highlighting
(setq claudemacs-process-environment
'("TERM=xterm-256color" "COLORTERM=truecolor"))
;; Add additional environment variables as needed
(add-to-list 'claudemacs-process-environment "CLAUDE_CODE_SYNTAX_HIGHLIGHT=off")
;; Whether to show system notifications when Claude is awaiting input (default: t)
(setq claudemacs-notify-on-await t)
;; Codex notification switches are enabled by default so notifications reach
;; the selected terminal backend.
;; Set to nil to use Codex's own notification method and focus condition.
;; (setq claudemacs-codex-notification-switches nil)
;; 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:
*claudemacs:claude:workspace-name**claudemacs:claude:/path/to/project**claudemacs:claude-2:workspace-name*The format is *claudemacs:TOOL(-N):SESSION-ID* where:
TOOL is the tool name (claude, codex, gemini, etc.)-N is the instance number (omitted for first instance)SESSION-ID is the workspace name or project pathCurrently supports Doom Emacs workspaces and Perspective mode. Open an issue if you use another workspace package.
The terminal backend provides a semi-character mode for typing directly to the AI tool and an Emacs mode for editing terminal contents. The keybindings below apply to both Eat and Ghostel.
When interacting with the terminal 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 in semi-char mode to send it to Claude Code, which uses it to paste an image from the clipboard.
Useful helper to copy the current file path with line number (or range) for pasting into Claude:
(defun copy-file-path-with-line ()
"Copy the current file path with line number (or range) to clipboard.
If a region is active, uses the range of lines."
(interactive)
(let* ((file-path (buffer-file-name))
(line-start (line-number-at-pos (if (use-region-p) (region-beginning) (point))))
(line-end (when (use-region-p) (line-number-at-pos (region-end))))
(result (if line-end
(format "%s:%d-%d" file-path line-start line-end)
(format "%s:%d" file-path line-start))))
(if file-path
(progn
(kill-new result)
(message "Copied: %s" result))
(message "Buffer is not visiting a file"))))
Bind it to a key and use it to quickly copy file references to paste into Claude.
Claude Code uses some keybindings that conflict with Emacs defaults. For example, Ctrl-g is used by Claude Code to edit your plan in your editor, but in Emacs C-g is the universal quit key (which claudemacs maps to send ESC to the terminal). In semi-char mode, both Eat and Ghostel provide C-q as a quoted-input escape hatch:
Use it to send the actual Ctrl-g character (or any other control character) to the terminal:
C-q C-g - Send Ctrl-g to Claude Code (e.g., to edit your plan)C-q C-c - Send Ctrl-c to Claude CodeC-q <any-key> - Send that key literally to the terminalThere can be a tricky interaction between a terminal emulator and Claude Code, because Claude Code uses input libraries that depend on terminal behavior. In Eat-mode this can cause the 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 Code 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. The same command is
available for both supported backends.
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.
Inspired by:
MIT License. See LICENSE file for details.
Emacs Lisp
97.7%
PowerShell
1.2%
Makefile
1.2%