SSH経由で入ってる端末でClaude Codeを利用してる際にShift + Enterで改行が入力できずに難儀したので。

同様のissueがGitHub上にもあったんだけどCloseされており、とりあえずGeminiに助けてもらいながら解決した。以下は備忘録だけど、書いたのは9割Claude Code。備忘録メインだからどうでもいい点ではあるけど、スタイルがちょっとあってないので、気が向いたらあとでこのブログのスタイルにあわせて修正するかも。

背景

Claude Code CLI では、プロンプト入力中に改行を挿入するために Shift+Enter を使う。 Kitty keyboard protocol 対応のターミナル(iTerm2, Kitty, WezTerm, Ghostty 等)ではローカル環境で設定不要で動作するが、SSH 経由だと動作しない場合がある。

問題の所在: なぜ SSH 越しだと Shift+Enter が効かないのか

Kitty keyboard protocol の仕組み

Claude Code は Shift+Enter の検出に Kitty keyboard protocol を使用している。このプロトコルは以下のように動作する:

  1. Claude Code が起動時にターミナルの種類を判定する(TERM_PROGRAM 等の環境変数を参照)
  2. 対応ターミナルと判定した場合、protocol 有効化シーケンスESC[>1u)をターミナルに送信する
  3. ターミナルが拡張モードに入り、Shift+Enter を ESC[13;2u(CSI u 形式)として送信するようになる
  4. Claude Code がこのシーケンスを受信し、改行として処理する
Claude Code → "\x1b[>1u" → ターミナル「拡張モード ON」
ターミナル  → Shift+Enter → "\x1b[13;2u" → Claude Code「改行だな」

SSH 経由で壊れる理由

SSH 接続では TERM_PROGRAM 等の環境変数がデフォルトではリモートに引き継がれない。

ローカル:  TERM_PROGRAM=iTerm.app → Claude Code が iTerm2 と認識 → protocol 有効化 ✅
SSH越し:   TERM_PROGRAM=(空)      → Claude Code が汎用端末と判定 → protocol 有効化しない ❌

重要なのは、SSH のバイト列転送自体は問題ないという点。手動で protocol を有効化すれば SSH 越しでも ESC[13;2u は正しく送受信される(実機確認済み)。問題は Claude Code がリモート側でターミナルを認識できず、有効化リクエストを送らないことにある。

検証方法

SSH 接続先で以下を実行し、Shift+Enter を押す:

# protocol を手動で有効化してから入力をキャプチャ
printf "\x1b[>1u"; cat -v
# → Shift+Enter を押して ^[[13;2u が表示されれば、SSH 経路は正常

⚠ 罠: Ctrl+C が効かなくなる

Kitty protocol を手動で有効化すると、Ctrl+C も CSI u エンコードされる^[[99;5u と表示される)。通常の SIGINT が発行されないため cat を終了できない。

脱出方法(優先順):

  1. 別のターミナルタブから kill する(推奨)

    # 別タブで同じサーバーに SSH 接続して実行
    pkill -f "cat -v"
  2. SSH エスケープシーケンスで切断する Enter → ~. を順番に押す(同時押しではない)。SSH 接続が切断される。

  3. タブを閉じる: iTerm2 のタブを Cmd+W で強制的に閉じて再接続する。

脱出後の事後処理:

プロンプトに戻った後、ターミナルは拡張プロトコルモードのままになっている。 以下を実行して元に戻す:

printf "\x1b[<1u"

それでも動作がおかしい場合は reset コマンドでターミナルを初期化する。

※ちなみに、Geminiに言われるままに作業してたらマジで抜けられなくなって困った。「困った」と文句をつけたときの応答が以下の通り:

[Gemini] 申し訳ありません、完全に私の案内不足で罠に嵌める形になってしまいました!

「罠に嵌める形になってしまいました!」じゃあないんだよ。確かにsshだからps-killすりゃいいんだけどさ。

解決方法

方法 1: SSH で環境変数を転送する(推奨)

TERM_PROGRAM をリモートに引き継がせることで、Claude Code にターミナルを正しく認識させる。

手順

1. サーバー側(Ubuntu): /etc/ssh/sshd_config を編集

- AcceptEnv LANG LC_*
+ AcceptEnv LANG LC_* TERM_PROGRAM TERM_PROGRAM_VERSION

編集後に SSH サービスを再起動:

sudo systemctl restart ssh    # Ubuntu は ssh(sshd ではない)

2. クライアント側(Mac): ~/.ssh/config に追加

Host <ホスト名>
    SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION

3. SSH 再接続して確認

echo $TERM_PROGRAM
# → iTerm.app と表示されれば OK

この状態で Claude Code を起動すれば、Shift+Enter で改行が入力できる。

対応ターミナル

ターミナル OS Kitty protocol 対応
iTerm2 macOS YES
Kitty macOS / Linux YES
WezTerm macOS / Linux / Windows YES
Ghostty macOS / Linux YES
Terminal.app macOS NO
GNOME Terminal Linux NO
Alacritty macOS / Linux /terminal-setup で設定可(macOS のみ)

方法 2: Ctrl+J(設定不要・どこでも動く)

Ctrl+J は ASCII LF(0x0A)を直接送信するため、protocol negotiation に依存せずどの環境でも改行として動作する。

  • SSH 経由でも、ローカルでも、どのターミナルでも同じ挙動
  • 設定一切不要
  • 欠点: Shift+Enter とは異なるキー操作に慣れる必要がある

方法 3: バックスラッシュ + Enter(設定不要)

行末に \ を入力してから Enter を押すと改行が挿入される。

> 1行目の内容\
> 2行目の内容\
> 3行目の内容
  • 設定一切不要
  • 欠点: タイプ量が少し増える

方法の選び方

SSH 経由で接続している?
├── YES → 手元のターミナルは Kitty protocol 対応?(上表参照)
│         ├── YES → 方法 1(環境変数転送の設定)
│         └── NO  → 方法 2(Ctrl+J)か 方法 3(バックスラッシュ)
└── NO(直接操作)→ Kitty protocol 対応ターミナルを使っていれば設定不要
                   → GNOME Terminal 等の場合は方法 2 か 方法 3

やってはいけないこと

iTerm2 で Shift+Enter を Hex Code 0a にマッピングしない

iTerm2 の Shift+Enter はネイティブで ESC[13;2u(CSI u 形式)を送信している。これを Hex Code 0a で上書きすると:

  • ローカルの Claude Code: CSI u ではなく bare LF が届くため、Shift+Enter として認識されなくなる可能性がある
  • 他のアプリ: Neovim 等 Kitty protocol を使うアプリで Shift+Enter が壊れる
  • 参考: GitHub Issue #614

参考リンク