Skip to content

Commit 4d4de58

Browse files
cvdubclaude
andcommitted
Add git worktree support for isolated editing sessions
Allow users to start Claude in a new git worktree so edits don't affect their working tree. Adds `claude-code-worktree` command (bound to `w` in command map and transient menu) that creates a worktree, starts Claude there, and optionally cleans up the worktree on buffer kill. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4a9914b commit 4d4de58

1 file changed

Lines changed: 88 additions & 1 deletion

File tree

claude-code.el

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ This must be set to the path of your Claude sandbox binary before use."
110110
:type '(choice (const :tag "Not configured" nil) string)
111111
:group 'claude-code)
112112

113+
(defcustom claude-code-worktree-confirm-cleanup t
114+
"Whether to prompt before removing the git worktree on buffer kill.
115+
116+
When non-nil, killing a Claude worktree buffer will ask for confirmation
117+
before running `git worktree remove'."
118+
:type 'boolean
119+
:group 'claude-code)
120+
113121
(defcustom claude-code-newline-keybinding-style 'newline-on-shift-return
114122
"Key binding style for entering newlines and sending messages.
115123
@@ -386,6 +394,7 @@ this history by adding `claude-code-command-history' to
386394
(define-key map (kbd "r") 'claude-code-send-region)
387395
(define-key map (kbd "s") 'claude-code-send-command)
388396
(define-key map (kbd "S") 'claude-code-sandbox)
397+
(define-key map (kbd "w") 'claude-code-worktree)
389398
(define-key map (kbd "t") 'claude-code-toggle)
390399
(define-key map (kbd "x") 'claude-code-send-command-with-context)
391400
(define-key map (kbd "y") 'claude-code-send-return)
@@ -410,6 +419,7 @@ this history by adding `claude-code-command-history' to
410419
("C" "Continue conversation" claude-code-continue)
411420
("R" "Resume session" claude-code-resume)
412421
("i" "New instance" claude-code-new-instance)
422+
("w" "Start in worktree" claude-code-worktree)
413423
("k" "Kill Claude" claude-code-kill)
414424
("K" "Kill all Claude instances" claude-code-kill-all)
415425
]
@@ -945,6 +955,44 @@ If not in a project and no buffer file return `default-directory'."
945955
;; Case 3: No project and no buffer file
946956
(t default-directory))))
947957

958+
(defvar-local claude-code--worktree-path nil
959+
"Path to the git worktree associated with this Claude buffer.
960+
961+
Set by `claude-code-worktree' for cleanup on buffer kill.")
962+
963+
(defun claude-code--git-root ()
964+
"Return the git repository root for `default-directory', or nil."
965+
(let ((root (ignore-errors
966+
(string-trim
967+
(with-output-to-string
968+
(with-current-buffer standard-output
969+
(process-file "git" nil t nil
970+
"rev-parse" "--show-toplevel")))))))
971+
(when (and root (not (string-empty-p root)))
972+
(file-name-as-directory root))))
973+
974+
(defun claude-code--remove-worktree (worktree-path)
975+
"Remove the git worktree at WORKTREE-PATH."
976+
(let ((default-directory (claude-code--git-root)))
977+
(if default-directory
978+
(let ((exit-code (process-file "git" nil nil nil
979+
"worktree" "remove" worktree-path)))
980+
(if (zerop exit-code)
981+
(message "Removed worktree %s" worktree-path)
982+
(message "Failed to remove worktree %s (exit %d)" worktree-path exit-code)))
983+
(message "Cannot remove worktree: not in a git repository"))))
984+
985+
(defun claude-code--maybe-cleanup-worktree ()
986+
"Clean up the git worktree when a Claude worktree buffer is killed.
987+
988+
Added to `kill-buffer-hook' by `claude-code-worktree'."
989+
(when claude-code--worktree-path
990+
(let ((wt-path claude-code--worktree-path))
991+
(if claude-code-worktree-confirm-cleanup
992+
(when (yes-or-no-p (format "Remove worktree %s? " wt-path))
993+
(claude-code--remove-worktree wt-path))
994+
(claude-code--remove-worktree wt-path)))))
995+
948996
(defun claude-code--find-all-claude-buffers ()
949997
"Find all active Claude buffers across all directories.
950998
@@ -1302,7 +1350,10 @@ With double prefix ARG (\\[universal-argument] \\[universal-argument]), prompt f
13021350

13031351
;; switch to the Claude buffer if asked to
13041352
(when switch-after
1305-
(pop-to-buffer buffer))))
1353+
(pop-to-buffer buffer))
1354+
1355+
;; Return the buffer
1356+
buffer))
13061357

13071358
;;;###autoload
13081359
(defun claude-code (&optional arg)
@@ -1392,6 +1443,42 @@ for the project directory."
13921443
;; Call claude-code--start with force-prompt=t
13931444
(claude-code--start arg nil t))
13941445

1446+
;;;###autoload
1447+
(defun claude-code-worktree (&optional arg)
1448+
"Start Claude in a new git worktree for isolated editing.
1449+
1450+
Passes --worktree to the Claude CLI, which handles worktree creation
1451+
and cleanup. Prompts for an optional worktree name; generates one
1452+
automatically if left empty. Sets the buffer's `default-directory'
1453+
to the worktree path so Emacs commands operate in the right directory.
1454+
1455+
With prefix ARG (\\[universal-argument]), switch to buffer after creating.
1456+
1457+
With double prefix ARG (\\[universal-argument] \\[universal-argument]),
1458+
prompt for the project directory."
1459+
(interactive "P")
1460+
(let* ((git-root (claude-code--git-root))
1461+
(_ (unless git-root (error "Not in a git repository")))
1462+
(name (read-string "Worktree name (empty for auto): "))
1463+
;; Generate a name if empty so we always know the path
1464+
(wt-name (if (string-empty-p name)
1465+
(format-time-string "wt-%Y%m%d-%H%M%S")
1466+
name))
1467+
(extra-switches (list "--worktree" wt-name))
1468+
(worktree-path (file-name-as-directory
1469+
(expand-file-name
1470+
(concat ".claude/worktrees/" wt-name)
1471+
git-root)))
1472+
;; Start Claude — CLI creates the worktree via --worktree
1473+
(buffer (claude-code--start arg extra-switches)))
1474+
;; Update buffer to reference the worktree directory
1475+
(when (buffer-live-p buffer)
1476+
(with-current-buffer buffer
1477+
(setq default-directory worktree-path)
1478+
(setq claude-code--worktree-path worktree-path)
1479+
(add-hook 'kill-buffer-hook
1480+
#'claude-code--maybe-cleanup-worktree nil t)))))
1481+
13951482
;;;###autoload
13961483
(defun claude-code-sandbox (&optional arg)
13971484
"Start Claude in sandbox mode using the configured sandbox binary.

0 commit comments

Comments
 (0)