§ Host operations — keeping the box alive
Why tmux refused the terminal — terminfo & the Ghostty fix
Why tmux refused the terminal — terminfo & the Ghostty fix
One day tmux ls on this box started answering:
missing or unsuitable terminal: xterm-ghostty
tmux was fine. The session was fine. What broke was a vocabulary lookup — and understanding why explains a whole layer of how terminals work.
What terminfo actually is
A terminal emulator (Ghostty, iTerm, GNOME Terminal…) is a program that draws text and interprets escape sequences — byte strings like \e[2J ("clear the screen") or \e[31m ("red text"). Different terminals support different sequences. So how does a program like tmux, vim, or htop know which bytes to emit for your terminal?
Two pieces:
$TERM— an environment variable the terminal sets to announce its identity. Ghostty setsTERM=xterm-ghostty. SSH forwards this variable to the remote host, so the server sees the client's terminal name.- The terminfo database — a directory of compiled capability files, one per terminal name, describing exactly which sequences that terminal speaks. Programs call
setupterm()(from ncurses), which looks up$TERMin the database.
Most programs degrade gracefully when the lookup fails. tmux does not — it refuses to start, because guessing wrong about the outer terminal corrupts everything it draws. Hence "missing or unsuitable terminal": the name xterm-ghostty simply wasn't on any shelf of this box's database.
Why the entry was missing — and why apt would never fix it
Ubuntu's terminfo database comes from the ncurses-term package. The version on this box (6.4+20240113) was snapshotted before Ghostty's entry existed, so the miss is expected. The surprising part: waiting for upgrades would never have fixed it.
The upstream ncurses maintainer carries a Ghostty entry only under the name ghostty — and, after a public disagreement with the Ghostty project, deliberately not under the alias xterm-ghostty, which is the name Ghostty actually puts in $TERM. Terminfo lookup is by exact name, so the upstream entry can never match. A fix had to be local and use Ghostty's own published entry.
The fix
Ghostty's authoritative terminfo (the same data infocmp -x xterm-ghostty would print on a machine with Ghostty installed) was taken from Arch Linux's ghostty-terminfo package — built straight from Ghostty's source — then decompiled and recompiled natively:
infocmp -x -A <extracted-pkg>/usr/share/terminfo xterm-ghostty > xterm-ghostty.ti
tic -x -o /etc/terminfo xterm-ghostty.ti
tic is the terminfo compiler; -x preserves extended (non-standard) capabilities like truecolor flags. The result lives at /etc/terminfo/x/xterm-ghostty (terminfo shards directories by first letter).
Why /etc/terminfo makes it permanent
ncurses on Debian/Ubuntu searches, in order:
~/.terminfo → /etc/terminfo → /lib/terminfo → /usr/share/terminfo
~/.terminfowould only fix one user;/etc/terminfofixes everyone./etc/terminfois admin-owned — no package installs into it, so noapt upgradecan overwrite or remove the entry.- Being earlier in the search path, it wins even if a future
ncurses-termships some conflictingghosttyentry.
Permanent by construction, not by hoping a package catches up.
Verifying (and the pty subtlety)
infocmp xterm-ghostty # resolves from /etc/terminfo
TERM=xterm-ghostty script -qec 'tmux ls' /dev/null # the real test
The script -qec … /dev/null wrapper matters: tmux only performs terminal setup when it has a tty. Run from a non-interactive shell (a pipe, an agent's exec tool), tmux ls succeeds even with a broken $TERM — a false negative. script allocates a pseudo-terminal, reproducing what an interactive SSH session experiences. A full tmux attach ⇄ detach round-trip was also verified this way.
Gotchas & reuse
- This will recur with any new terminal emulator whose
$TERMpredates the box's ncurses snapshot (it famously happened with kitty'sxterm-kittyfor years). Same fix: get the terminal's own terminfo,tic -x -o /etc/terminfo. - The portable source is kept at
/root/snippets/xterm-ghostty.ti— on any other host:tic -x -o /etc/terminfo xterm-ghostty.ti(or without root:tic -x xterm-ghostty.ti, which installs to~/.terminfo). - The client-side alternative (run from the laptop) is Ghostty's documented one-liner:
infocmp -x xterm-ghostty | ssh host -- tic -x -. - Inside tmux panes
$TERMistmux-256color/screen-256color(set by tmux itself); thexterm-ghosttyentry matters for the outer client connection only.