Ghosttyの全タブがClaude Codeになる問題をzshフックで直す
目次
Ghosttyで複数プロジェクトを開いて、各タブに2〜3ペインずつ切ってClaude Codeを走らせると、タブバーが全部 Claude Code になる。
元記事の Liran Baba 氏は、4つのGhosttyタブを開いていた朝に claudoscope のテスト実行中タブを探して、タブ切り替えとペイン確認を何度も繰り返していた。
tmuxでClaude CodeとCodexを連携させた自動ループや、かなチャット v3のように、複数のAI CLIを同時に立てる運用では、いま見ているペインがどのリポジトリで、AIエージェントが走っているのかをタブバーで読めないと普通に迷う。
タイトルを書き換える相手が二重にいる
Liran Baba の記事で整理されていた原因は2つある。
Ghosttyのshell integrationが、フォアグラウンドプロセス名をターミナルタイトルに入れる。
プロンプトなら zsh、Claude Code実行中なら claude、Node実行中なら node という具合だ。
もう1つがClaude Code側のタイトル更新。
Claude Codeはターミナルタイトルを自動更新し、会話コンテキストに基づいたタイトルを出す。
公式の環境変数一覧にも CLAUDE_CODE_DISABLE_TERMINAL_TITLE があり、1 にするとこの自動更新を止められる。
Ghostty側だけを止めても、Claude CodeがOSC 2で上書きする。
Claude Code側だけを止めても、Ghosttyのshell integrationがフォアグラウンドプロセス名へ戻す。
両方を退かせてから、自分のzshフックでタイトルを決める、という順番になる。
Ghosttyにはタイトルだけ触らせない
Ghostty側は shell-integration-features = no-title を入れる。
shell integration全部を切るのではなく、タイトル更新だけ外す設定だ。
shell-integration-features = no-title
Ghosttyのshell integrationには、プロンプト境界の把握、作業ディレクトリ引き継ぎ、jump_to_prompt、SSH関連など、タイトル以外の便利機能もある。
全部切る shell-integration = none ではなく no-title にするほうが副作用が小さい。
macOSでは設定ファイルの置き場所も確認する。
元記事では ~/.config/ghostty/config と ~/Library/Application Support/com.mitchellh.ghostty/config のどちらをGhosttyが見ているかに触れていた。
dotfiles管理したいならXDG側へ寄せ、Application Support側からシンボリックリンクする運用が扱いやすい。
Claude Codeには環境変数で黙ってもらう
Claude Code側は ~/.claude/settings.json の env に入れる。
{
"env": {
"CLAUDE_CODE_DISABLE_TERMINAL_TITLE": "1"
}
}
シェルでexportしてから claude を起動してもいいが、毎回の起動で忘れないようにするならsettings側が楽だ。
この変数は公式ドキュメント上では「会話コンテキストに基づく自動ターミナルタイトル更新を無効化する」設定として載っている。
CTXでClaude Codeに動くメモリを足すでも書いた通り、Claude Codeのフックや設定は便利な一方で、既存設定との衝突が見えにくい。
settings.json をすでに共有しているなら、タイトル用のenvだけを雑に足すのではなく、ユーザー設定、プロジェクト設定、ローカル設定のどこで効かせたいかを分けたほうがいい。
zshのpreexecで起動中だけ印を付ける
ここから先はzsh側でタイトルを自分で書く。
元記事の30行フックは、gitリポジトリ名を通常タイトルにし、claude 起動時だけ印を付ける構成だった。
絵文字を使うと環境によって幅が揺れるので、ここでは CC にしている。
autoload -U add-zsh-hook
function _ghostty_context() {
local ctx
ctx=$(git rev-parse --show-toplevel 2>/dev/null) && echo "${ctx:t}" || echo "${PWD:t}"
}
function _ghostty_set_title() {
printf '\033]2;%s\007' "$(_ghostty_context)"
}
function _ghostty_set_title_for_cmd() {
local cmd="$1"
local ctx="$(_ghostty_context)"
case "$cmd" in
claude|claude\ *|clauded|clauded\ *)
printf '\033]2;CC %s\007' "$ctx"
;;
esac
}
add-zsh-hook chpwd _ghostty_set_title
add-zsh-hook precmd _ghostty_set_title
add-zsh-hook preexec _ghostty_set_title_for_cmd
chpwd は cd した直後に走る。
別リポジトリへ移動した時点でタブ名が変わる。
preexec はコマンド実行直前に走る。
ここで claude や clauded を拾い、リポジトリ名の前に CC を付ける。
precmd は次のプロンプト表示前に走る。
Claude Codeが終了したあと、タブ名をリポジトリ名へ戻す役割になる。
zshの preexec はalias展開後のコマンド行を見る。
clauded='claude --dangerously-skip-permissions' のようなaliasなら claude * で拾える。
関数やスクリプトとして clauded を作っている場合はalias展開ではないので、clauded|clauded\ * を残しておく意味がある。
split単位の限界は残る
元記事で一番大事だった制限は、GhosttyのOSC 2が永続的なタブ単位タイトルではなく、surface、つまりsplit側のタイトルに寄る点だ。
タブに3つのsplitがあり、1つだけClaude Codeを走らせている場合、タブバーの表示はフォーカス中のsplitに引っ張られる。
これはzshフックでは直せない。
Ghostty側で永続的なタブレベルタイトルを持つか、タブ色をOSCで変えられるようになるまでは、1タブ1プロジェクト、1split1役割くらいまでが扱いやすい線になる。
Ghostty discussion #7581 と issue #12235 がこのあたりの未解決点として挙げられている。
AI CLIを増やすなら、case文に gemini や cursor-agent、codex を足せば同じ考え方で動く。
ただ、タブタイトルに詰め込みすぎるとまた読めなくなる。
自分なら、通常時はリポジトリ名、Claude Code実行中だけ CC repo、危険な権限モードは CC! repo くらいに留める。
セッション管理より手前の視認性
Claude Codeのコンテキスト劣化では、長時間セッションで同じファイルを読み直したり、前に捨てた案へ戻ったりする兆候を書いた。
タブタイトルが全部同じになる問題は、モデル側ではなく人間側のコンテキスト劣化に近い。
目の前のタブがどのリポジトリか分からないまま、別のセッションへ指示を投げる事故が起きる。
Claude CodeやCodexを複数走らせるなら、タブ名、tmuxセッション名、ログファイル名、git worktree名を同じ粒度で揃えたほうがいい。
フックでタブ名だけ直しても、ログや作業ディレクトリの命名がばらばらだと、結局どこかで迷う。