CODEX // oaoisme wiki

§ Host operations — keeping the box alive

Why tmux refused the terminal — terminfo & the Ghostty fix

updated 2026-06-10

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:

  1. $TERM — an environment variable the terminal sets to announce its identity. Ghostty sets TERM=xterm-ghostty. SSH forwards this variable to the remote host, so the server sees the client's terminal name.
  2. 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 $TERM in 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
  • ~/.terminfo would only fix one user; /etc/terminfo fixes everyone.
  • /etc/terminfo is admin-owned — no package installs into it, so no apt upgrade can overwrite or remove the entry.
  • Being earlier in the search path, it wins even if a future ncurses-term ships some conflicting ghostty entry.

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 $TERM predates the box's ncurses snapshot (it famously happened with kitty's xterm-kitty for 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 $TERM is tmux-256color/screen-256color (set by tmux itself); the xterm-ghostty entry matters for the outer client connection only.