@@ -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