[BUG] Ctrl+G editor is spawned from the bg-spare daemon without a controlling terminal - emacs cannot launch (exit 1)

Status Open
Reported on v2.1.139
Maintainer reply None cached
Activity 0 comments · opened Aug 22, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

With EDITOR=emacs (or emacs -nw), pressing Ctrl+G fails immediately with Emacs quit unexpectedly (exit code 1) — the editor never appears. vim works in the same setup, which makes this look like an emacs problem; it isn't.

Root cause: since roughly 2.1.139, interactive sessions can be adopted by a pre-warmed daemon process (claude bg-spare, a child of claude bg-pty-host — visible in ps with ?? in the TTY column) instead of running in the foreground claude process. On my machine this happens consistently; when a session does run in-process, Ctrl+G works fine, which may explain why reports of this are inconsistent across setups.

The editor is spawned from that daemon process with spawnSync(..., {stdio: "inherit"}). The child inherits fds pointing at the session pty but has no controlling terminal, because nothing in the daemon's process tree ever acquired one. Most editors don't care; emacs does — during terminal init it opens the literal device /dev/tty, which requires a controlling terminal, so it exits on the spot.

The timing lines up with #58664, which bisected a Ctrl+G regression to the 2.1.132 → 2.1.138/139 window, exactly when the daemon architecture shipped. #19076 (nano/micro failing over ssh) may be related; both were closed by the stale-bot without a diagnosis. #83006 shows the same error string but is a different bug (content discarded when an editor exits nonzero) — filing separately since this one is about the editor being unable to launch at all.

What Should Happen?

Ctrl+G opens the editor named by $EDITOR/$VISUAL, as it does when the session runs in-process (and as it did before ~2.1.138).

Error Messages/Logs

Emacs quit unexpectedly (exit code 1)

# and, captured from the spawned emacs's stderr:
emacs: Could not open file: /dev/tty

Steps to Reproduce

  1. macOS, EDITOR=emacs exported (emacs 30.x, terminal build).
  2. Start claude in a terminal and confirm the session is daemon-hosted: ps ax -o pid,ppid,tty,command | grep bg-spare shows a claude bg-spare process with ?? in the TTY column hosting the session.
  3. Press Ctrl+G.
  4. The error appears immediately; emacs never launches. Pointing EDITOR at a logging wrapper shows the spawned process has stdin on the session pty but open("/dev/tty") fails, because the process has no controlling terminal.
  5. Counterexample: in a session that runs in-process (no bg-spare ancestor), the same Ctrl+G works fine.

Observed over ssh (Ghostty) and reproduced locally; the launch failure does not depend on the terminal emulator.

Claude Model

Any

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.132 (last confirmed working per #58664's bisect)

Claude Code Version

2.1.239 (Claude Code)

Platform

Claude Code CLI

Operating System

macOS

Terminal/Shell

Ghostty

Additional Information

Workaround, part 1 — get emacs to start. Emacs's -t DEVICE flag opens a named terminal device instead of /dev/tty, and stdin is a valid pty in the daemon spawn. Wrapper as $EDITOR:

#!/bin/sh
if t=$(tty); then
  exec emacs -nw -t "$t" "$@"
fi
exec emacs -nw "$@" </dev/tty >/dev/tty 2>/dev/tty

Workaround, part 2 — fix the keyboard (ssh + Ghostty). With the wrapper above, emacs opens, but in my setup every modified key then arrived CSI-u encoded (Ctrl-C showed up as literal ;5u text, so there was no way to quit). I confirmed the kitty-protocol flags were 0 immediately before emacs started, yet modified keys arrive CSI-u encoded once emacs is up — possibly emacs's own modifyOtherKeys handshake combined with how Ghostty encodes that mode; I haven't pinned down which layer enables it. Teaching emacs to decode it fixes the symptom regardless of the cause; shim loaded via -l:

;; csi-u-decode.el: map CSI-u key sequences to normal emacs key events
(defun csi-u--event (code mods)
  (let ((bits (1- mods)))
    (event-convert-list
     (append (when (/= 0 (logand bits 4)) '(control))
             (when (/= 0 (logand bits 2)) '(meta))
             (when (/= 0 (logand bits 1)) '(shift))
             (list code)))))
(dolist (code (append (number-sequence 32 126) '(9 13 27 127)))
  (dolist (mods (number-sequence 2 16))
    (ignore-errors
      (define-key input-decode-map (format "\e[%d;%du" code mods)
                  (vector (csi-u--event code mods))))))
(define-key input-decode-map "\e[27u" [?\e])

So the happy path becomes exec emacs -nw -t "$t" -l /path/to/csi-u-decode.el "$@". With both pieces, Ctrl+G → edit → C-x C-c round-trips correctly through the daemon.

Suggested fix: give the spawned editor a controlling terminal before exec — the standard login_tty(3) sequence (setsid(), then ioctl(TIOCSCTTY) on the pty), the same thing tmux does for processes it spawns on ptys it owns. In a synthetic reproduction of the daemon's spawn topology both calls succeed (the spawned child is not a session leader and the pty is unclaimed), so this looks like a small change. There's currently no user-facing way to disable the daemon/spare architecture, so until then a wrapper is the only client-side option.

View original on GitHub ↗