Summary
There is no way to change which keys insert a newline in the composer. The bindings are fixed at Shift+Enter, Ctrl+Enter and \+Enter, and nothing in ~/.factory/settings.json or the CLI overrides them. Option+Enter in particular cannot be bound at all, which is the one that costs me: it is the newline key I have muscle memory for from other terminal agents, and on the hosts I run droid on it is also the only modified-Enter that reliably survives the terminal chain.
Current behaviour (droid 0.204.0)
The composer's newline handler accepts exactly two byte sequences:
\x1b[13;2u (Shift+Enter, CSI-u / xterm modifyOtherKeys=2)
\x1b[27;2;13~ (Shift+Enter, CSI 27 form)
The help label confirms the set: insert-newline is documented as Shift+Enter / Ctrl+Enter / \+Enter.
There is no handler for a meta-modified Return anywhere in the input path, so the Option+Enter encodings do nothing useful:
\x1b[13;3u and \x1b[27;3;13~ (Option+Enter under the kitty keyboard protocol) fall through unhandled
\x1b\r (classic ESC-prefixed Alt+Enter) hits the composer as ESC followed by CR
The submit guard is return && !ctrl && !meta && !input.includes("\x1b"), so a meta Return is at least not treated as submit, but it is not treated as a newline either.
Why a fixed set is a problem
Shift+Enter is the fragile one to depend on, because it only exists as a distinct key at all if the whole chain negotiates an extended-key protocol. Mine is:
Ghostty -> mosh -> tmux 3.6 -> ssh -> tmux 3.4 -> droid
The middle pane there is ssh, which never requests extended keys, so tmux -s extended-keys on collapses Shift+Enter to a bare CR before it reaches droid and the message submits. I fixed that by setting extended-keys always on both tmux servers, which forces the CSI-u form down the chain whether or not anything asked for it. That works, but it is a blunt setting that also hands CSI-u to every other TUI in those panes, and it is not something a user should need to reason about to get a newline.
Option+Enter has none of that fragility: every terminal I use emits something distinct for it out of the box. droid just does not listen for it.
Request
Either of these would solve it, in preference order:
- A keymap section in
~/.factory/settings.json mapping intents to key names, e.g.
{
"keybindings": {
"insert-newline": ["shift+enter", "alt+enter", "ctrl+enter"]
}
}
- Failing that, add
\x1b[13;3u, \x1b[27;3;13~ and \x1b\r to the sequences that insert-newline already accepts. That covers Option+Enter across kitty-protocol and legacy terminals with no configuration at all, and no other droid binding claims those bytes today.
Broader keymap support would also cover the neighbouring cases people hit (rebinding submit, escape, history navigation), but the newline set is the one that is actively unusable as shipped.
Workaround for anyone else hitting this
Remap the key in the terminal so droid sees the Shift+Enter form it already accepts. Ghostty:
keybind = shift+enter=text:\x1b[13;2u
keybind = alt+enter=text:\x1b[13;2u
Plus set -s extended-keys always in tmux if droid runs on the far side of an ssh hop.
Environment
- droid 0.204.0 (Homebrew cask, macOS arm64)
- macOS 15 / Darwin 25.5.0
- Ghostty 1.3.1
- tmux 3.6b (mac), tmux 3.4 (Ubuntu VMs)
Summary
There is no way to change which keys insert a newline in the composer. The bindings are fixed at Shift+Enter, Ctrl+Enter and
\+Enter, and nothing in~/.factory/settings.jsonor the CLI overrides them. Option+Enter in particular cannot be bound at all, which is the one that costs me: it is the newline key I have muscle memory for from other terminal agents, and on the hosts I run droid on it is also the only modified-Enter that reliably survives the terminal chain.Current behaviour (droid 0.204.0)
The composer's newline handler accepts exactly two byte sequences:
\x1b[13;2u(Shift+Enter, CSI-u / xterm modifyOtherKeys=2)\x1b[27;2;13~(Shift+Enter, CSI 27 form)The help label confirms the set:
insert-newlineis documented asShift+Enter / Ctrl+Enter / \+Enter.There is no handler for a meta-modified Return anywhere in the input path, so the Option+Enter encodings do nothing useful:
\x1b[13;3uand\x1b[27;3;13~(Option+Enter under the kitty keyboard protocol) fall through unhandled\x1b\r(classic ESC-prefixed Alt+Enter) hits the composer as ESC followed by CRThe submit guard is
return && !ctrl && !meta && !input.includes("\x1b"), so a meta Return is at least not treated as submit, but it is not treated as a newline either.Why a fixed set is a problem
Shift+Enter is the fragile one to depend on, because it only exists as a distinct key at all if the whole chain negotiates an extended-key protocol. Mine is:
The middle pane there is
ssh, which never requests extended keys, sotmux -s extended-keys oncollapses Shift+Enter to a bare CR before it reaches droid and the message submits. I fixed that by settingextended-keys alwayson both tmux servers, which forces the CSI-u form down the chain whether or not anything asked for it. That works, but it is a blunt setting that also hands CSI-u to every other TUI in those panes, and it is not something a user should need to reason about to get a newline.Option+Enter has none of that fragility: every terminal I use emits something distinct for it out of the box. droid just does not listen for it.
Request
Either of these would solve it, in preference order:
~/.factory/settings.jsonmapping intents to key names, e.g.{ "keybindings": { "insert-newline": ["shift+enter", "alt+enter", "ctrl+enter"] } }\x1b[13;3u,\x1b[27;3;13~and\x1b\rto the sequences thatinsert-newlinealready accepts. That covers Option+Enter across kitty-protocol and legacy terminals with no configuration at all, and no other droid binding claims those bytes today.Broader keymap support would also cover the neighbouring cases people hit (rebinding submit, escape, history navigation), but the newline set is the one that is actively unusable as shipped.
Workaround for anyone else hitting this
Remap the key in the terminal so droid sees the Shift+Enter form it already accepts. Ghostty:
Plus
set -s extended-keys alwaysin tmux if droid runs on the far side of an ssh hop.Environment