Summary
Starting a Claude session from a single-window frame aborts mid-setup with
delete-window: Attempt to delete minibuffer or sole ordinary window.... The
session’s vterm buffer is left alive under the raw vterm-mode-map, so the
claude-code-newline-keybinding-style bindings are never installed — both RET
and S-RET just submit, and there is no way to insert a newline.
The error originates in claude-code--term-make (vterm backend), which calls
delete-window unconditionally after pop-to-buffer. On a single-window frame,
pop-to-buffer reuses the sole window rather than creating a new one, so there
is no window to delete and delete-window signals — aborting claude-code--start
before it reaches the buffer-setup block that installs the keymap (and the
vterm-copy-mode restore hook).
Environment
- claude-code.el: 0.4.5 (
main @ 03199df)
- Backend:
vterm (claude-code-terminal-backend = 'vterm)
claude-code-newline-keybinding-style: 'newline-on-shift-return
- Emacs: GNU Emacs 29.3 (X toolkit / Xaw3d), Debian, running under Wayland (XWayland)
- Launch context: Emacs server (
server-start) + emacsclient; sessions started from a frame that currently shows a single window
Note: the package declares (emacs "30.0"); I’m on 29.3, but this is a pure
window-topology logic error independent of Emacs version — the same
sole-window delete-window signal occurs identically on Emacs 30.
Steps to reproduce
- Be in a frame with one window (no splits).
M-x claude-code (vterm backend).
Expected
Session starts, the claude-code keymap is installed, and S-RET inserts a
newline while RET submits (per newline-on-shift-return).
Actual
*Backtrace* (with debug-on-error):
Debugger entered--Lisp error: (error "Attempt to delete minibuffer or sole ordinary window...")
delete-window(#<window 7 on *claude:.../:default*>)
(save-current-buffer (set-buffer buffer) (pop-to-buffer buffer) (vterm-mode)
(delete-window (get-buffer-window buffer)) buffer)
...
claude-code--term-make(vterm "*claude:.../:default*" "claude" nil)
... claude-code--start(nil nil)
claude-code(nil)
The buffer still exists with Claude running, but under the bare vterm-mode-map.
C-h k on S-RET in that buffer confirms the claude-code map was never applied:
<return> (translated from S-<return>) runs the command
vterm-send-return (found in vterm-mode-map)
(Both RET and S-RET resolve to vterm-send-return → submit.)
Root cause
claude-code--term-make for the vterm backend:
(pop-to-buffer buffer)
(vterm-mode)
(delete-window (get-buffer-window buffer)) ; <-- assumes pop-to-buffer made a new window
buffer
The comment explains pop-to-buffer is used so vterm has a live window to size
the PTY. But pop-to-buffer only creates a window when it can’t reuse one. On a
single-window frame it reuses the sole window, so delete-window on that window
signals “sole ordinary window”. Because term-make is called early in
claude-code--start, the signal aborts before the with-current-buffer block
that runs claude-code--term-configure and claude-code--term-setup-keymap — so
neither the keybindings nor the vterm-copy-mode restore hook are installed.
This is distinct from #69 (there the map installs and works, then copy-mode
clobbers it). Here it’s never installed. (The same unconditional delete-window
pattern also appears later in the file, ~L1951, and may be worth auditing.)
Suggested fix
Only delete the window if pop-to-buffer actually created one — i.e. when the
buffer’s window is part of a split, not the frame’s sole window:
(let ((win (get-buffer-window buffer)))
(when (and win (window-parent win)) ; non-nil => it's a split, safe to delete
(delete-window win)))
(window-parent returns nil for a frame’s sole/root window.) A minimal
alternative is to wrap just this call in ignore-errors.
Workaround (config-side)
(with-eval-after-load 'claude-code
(advice-add 'claude-code--term-make :around
(lambda (orig &rest args)
(cl-letf* ((real-delete (symbol-function 'delete-window))
((symbol-function 'delete-window)
(lambda (&optional window)
(condition-case nil (funcall real-delete window)
(error nil)))))
(apply orig args)))
'((name . tolerate-sole-window))))
Summary
Starting a Claude session from a single-window frame aborts mid-setup with
delete-window: Attempt to delete minibuffer or sole ordinary window.... Thesession’s vterm buffer is left alive under the raw
vterm-mode-map, so theclaude-code-newline-keybinding-stylebindings are never installed — bothRETand
S-RETjust submit, and there is no way to insert a newline.The error originates in
claude-code--term-make(vterm backend), which callsdelete-windowunconditionally afterpop-to-buffer. On a single-window frame,pop-to-bufferreuses the sole window rather than creating a new one, so thereis no window to delete and
delete-windowsignals — abortingclaude-code--startbefore it reaches the buffer-setup block that installs the keymap (and the
vterm-copy-moderestore hook).Environment
main@03199df)vterm(claude-code-terminal-backend='vterm)claude-code-newline-keybinding-style:'newline-on-shift-returnserver-start) +emacsclient; sessions started from a frame that currently shows a single windowSteps to reproduce
M-x claude-code(vterm backend).Expected
Session starts, the claude-code keymap is installed, and
S-RETinserts anewline while
RETsubmits (pernewline-on-shift-return).Actual
*Backtrace*(withdebug-on-error):The buffer still exists with Claude running, but under the bare
vterm-mode-map.C-h konS-RETin that buffer confirms the claude-code map was never applied:(Both
RETandS-RETresolve tovterm-send-return→ submit.)Root cause
claude-code--term-makefor the vterm backend:The comment explains
pop-to-bufferis used so vterm has a live window to sizethe PTY. But
pop-to-bufferonly creates a window when it can’t reuse one. On asingle-window frame it reuses the sole window, so
delete-windowon that windowsignals “sole ordinary window”. Because
term-makeis called early inclaude-code--start, the signal aborts before thewith-current-bufferblockthat runs
claude-code--term-configureandclaude-code--term-setup-keymap— soneither the keybindings nor the
vterm-copy-moderestore hook are installed.This is distinct from #69 (there the map installs and works, then copy-mode
clobbers it). Here it’s never installed. (The same unconditional
delete-windowpattern also appears later in the file, ~L1951, and may be worth auditing.)
Suggested fix
Only delete the window if
pop-to-bufferactually created one — i.e. when thebuffer’s window is part of a split, not the frame’s sole window:
(
window-parentreturns nil for a frame’s sole/root window.) A minimalalternative is to wrap just this call in
ignore-errors.Workaround (config-side)