#16273·helix

Shell prompt is misplaced when exiting Helix after resizing tmux pane

Author: MarSoftCreated Sep 13, 2026Updated Sep 13, 2026
LabelsC-bug

Summary

On exit, Helix sends the "leave alternate screen" sequence (CSI ? 1049 l) twice:

  • once from Application::runrestore_term()TerminaBackend::restore(),
  • and again from impl Drop for TerminaBackend, which unconditionally re-sends the same reset sequence.

DECRST 1049 means "switch to the normal screen and restore the cursor saved by DECSET 1049". xterm and tmux perform that cursor restore even when the alternate screen is already off. So if the terminal was resized while Helix was open, the first ?1049l correctly restores and reflows the normal screen and moves the cursor with it, and the second ?1049l then overwrites the cursor with the stale, pre-resize saved position. The shell prompt is printed in the middle of the scrollback, over old output.

Without a resize both positions coincide, so the bug is invisible. It appeared with the switch from Crossterm to Termina (#13307, released in 25.07): the Crossterm backend had no Drop impl. Current master still has it.

Reproduction Steps

Observe asciinema here: https://asciinema.org/a/gNuOg0mMU6IXWxsV

Tested with tmux 3.6a; xterm's ctlseqs documents the same DECRST 1049 semantics, "restore cursor as in DECRC", so it is not tmux-specific):

  1. tmux new -s t -x 80 -y 24, then produce some scrollback: seq 1 30
  2. hx somefile (or just hx)
  3. Make the pane taller, e.g. tmux resize-window -y 40 from another window
  4. :q

Expected: the new prompt appears on the bottom row, below the previous output. Actual: the prompt appears ~10 rows up, on top of the earlier output.

The same result without Helix, proving it is the duplicate ?1049l:

printf '\e[?1049h'; sleep 3; printf '\e[?1049l\e[?1049l'   # resize the pane during the sleep

With a single \e[?1049l (or with vim / less) the prompt lands correctly.

Captured Helix exit bytes (tmux pipe-pane), showing the reset block sent twice:

^[[?25h^[[0 q^[[?2004l^[[?1004l^[[?1049l                  <- restore()
^[[?1000l^[[?1002l^[[?1003l^[[?1015l^[[?1006l^[[2 q^[[34h  <- drop(): disable_extensions/mouse
^[[?25h^[[0 q^[[?2004l^[[?1004l^[[?1049l                  <- drop(): reset block again

Helix log

Not relevant (no error is logged).

Proposed fix (tested in my environment)

Track whether the terminal is claimed (claim() sets a flag, restore() clears it) and make Drop skip the reset when restore() already ran. Drop still resets the terminal if restore() was never called (e.g. an early error). PR: #TODO

Platform

Linux (NixOS)

Terminal Emulator

tmux 3.6a

Installation Method

nix flake

Helix Version

helix 25.07.1 (ef7869d8)