Skip to content
BONFIRE LIT
Go back

SSH越しのmodifyOtherKeysでエスケープシーケンスが化けた話

Linuxホスト(Omarchy OS)からUbuntuにSSH接続して作業していたら、 大文字を打つたびに文字ではなくエスケープシーケンスが表示される 症状に遭遇した。原因はmodifyOtherKeysのプロトコル不整合で、最終的に.bashrcで明示的に無効化することに落ち着いた。記録として残しておく。

TL;DR

~/.bashrcの末尾に以下を追記:

# Disable terminal modifyOtherKeys
case "$-" in *i*) printf '\e[>4;0m' ;; esac

症状

SSH先のプロンプトでcat -vして大文字を入力すると、こうなる:

~ ❯ cat -v
^[[27;2;86~       ← Shift+V を打った
a^[[27;2;65~      ← a, Shift+A を打った

通常はVAが表示されるはずが、生のエスケープシーケンスが流れてくる。

エスケープシーケンスを読み解く

^[[27;2;86~という形は、本来こうあるべきだったエスケープシーケンスの「中身だけ」が見えている状態:

ESC [ 27 ; 2 ; 86 ~
        ↑   ↑   ↑
       prefix modifier code
       (固定) (2 = Shift) (86 = ASCII 'V')

ASCIIで読み解くと:

数値文字
86V
65A

つまり「Shift + V」「Shift + A」という打鍵情報が、文字に変換されず生のシーケンスのまま流れていた。

原因: modifyOtherKeysのプロトコル不整合

これはmodifyOtherKeys(xterm拡張キーエンコーディング)が、ローカル端末では有効化されているのに、リモート側のbash/readlineで解釈されていない、という現象。

[ローカル端末 (Ghostty)]   [SSH]              [Ubuntu側 bash]
modifyOtherKeys=2 が ON   → そのまま中継 →   readline はこのプロトコルを知らない
Shift+V を ESC[27;2;86~                       → 文字列としてそのまま表示
として送信

切り分け

Step 1: TERMを揃える(失敗)

最初に疑ったのはTERM環境変数の不一致。リモートで設定し直してみた:

export TERM=xterm-256color

→ 直らない。

Step 2: ローカル側を疑う

SSHしない状態で、ローカル端末でそのままcat -vを実行:

~ ❯ cat -v
^[[27;2;86~

SSHを経由していないローカル単体でも、すでにエスケープが出ている。 → 端末エミュレータ自体が無条件でmodifyOtherKeysを送っていると確定。SSH/リモートの問題ではなく、ローカル端末側の挙動の問題だった。

TERMを変えても直らなかったのは筋が通る話で、TERMの問題は 「相手にどう見えているか」modifyOtherKeysの問題は 「相手に何を喋るか」 という別レイヤーの話だから。

Step 3: 端末を特定する

使っている端末エミュレータを特定:

~ ❯ ps -o comm= -p $(ps -o ppid= -p $$)
ghostty

解決策: .bashrcで明示的に無効化

~/.bashrcの末尾に以下を追記:

# Disable terminal modifyOtherKeys
case "$-" in *i*) printf '\e[>4;0m' ;; esac

シェル起動のたびに端末に対して「もう拡張で送るな」と通告する形。

なぜ端末側ではなく .bashrc で無効化するか

Ghostty自体の設定でmodifyOtherKeysを切ることもできるが、Ghosttyは状況や他の設定の影響でmodifyOtherKeysが再びONになるケースがある。 一方、 シェル側で起動時に明示的に無効化 しておけば、端末側の状態に依存しない。

Ghosttyを使う構成では、.bashrcでの無効化をベースラインに置くのがベストだと思う。

まとめ


Share this post on: