🖥️

Claude Code を並列で回す WezTerm ターミナル構成

に公開

はじめに

こんにちは、クラシルでバックエンドエンジニアをしている hama です。
AIエージェントの進化により、私自身の開発スタイルが変わり、AIエージェントとの対話が開発プロセスの大部分を占めるようになりました。

こうした変化のなかで、VSCode から WezTerm + Neovim に移行しました。本記事では、AIエージェント(主に Claude Code)を中心に据えたターミナル構成と、日々の開発で使っている工夫を紹介します。

なぜ VSCode を離れたのか

もともと VSCode を使って開発していました。ただ、マルチリポジトリ構成のサービスを扱っているため、リポジトリごとに VSCode のウィンドウを開く必要があり、常にいくつものウィンドウが散乱している状態でした。

そこに Claude Code をはじめとする AIエージェントが大きく進化し、ほとんどの作業が AIエージェントを通じて実施されるようになりました。コードを直接触るのは細部の修正くらいです。自分のワークスタイルでは、リッチなエディタ機能を活かす場面が減ってきた一方で、ウィンドウの散乱という課題はそのまま残っていました。

それなら ターミナルで全て完結する環境 を作り、タブで複数リポジトリをスッキリ管理したほうがいいのではないか。そう考えて WezTerm + Neovim での開発環境を構築し始めました。VSCode が悪いわけではなく、自分の課題に合わせた選択です。

WezTerm の構成

タブ = よく使うディレクトリへの即アクセス

日常的にアクセスするディレクトリがいくつかあります。

  • リポジトリルート(マルチリポジトリなので複数)
  • レビュー用リポジトリ
  • Obsidian の保管庫(Vault)のルート
  • Downloads フォルダ

これらに素早くアクセスするためのコマンドを用意し、それぞれ専用のタブとして開くようにしました。

コマンド 用途 タブ名
wg リポジトリルートへ移動(fzf + ghq でインクリメンタルに選択) リポジトリ名
wr レビュー用に clone したリポジトリを開く リポジトリ名(review)
wm Obsidian のメモ(Vault ルート)を開く memo
wd Downloads フォルダを開く Downloads
~/.config/zsh/.zsh.d/wezterm-ghq.zsh
# Weztermで新しいタブを開く共通関数
function _wezterm_new_tab() {
    local dir="$1"
    local tab_name="${2:-$(basename ${dir})}"
    local pane_id=$(wezterm cli spawn --cwd "${dir}")
    wezterm cli set-tab-title --pane-id "${pane_id}" "${tab_name}"
    wezterm cli activate-pane --pane-id "${pane_id}"
}

# ghqリポジトリをWeztermの新しいタブで開く
function wg() {
    local root="$(ghq root)"
    local repo="$(ghq list | fzf --reverse --height=50% --preview="ls -AF --color=always ${root}/{1}")"
    [ -z "${repo}" ] && return
    _wezterm_new_tab "${root}/${repo}"
}

# memoをWeztermの新しいタブで開く
function wm() {
    _wezterm_new_tab "$HOME/Documents/Obsidian/memo" "memo"
}

# DownloadsをWeztermの新しいタブで開く
function wd() {
    _wezterm_new_tab "$HOME/Downloads" "Downloads"
}

# ~/work/review配下のリポジトリをインクリメンタルサーチで選んでレビュー用タブを開く
function wr() {
    local review_root="$HOME/work/review"
    local repo="$(ls "${review_root}" | fzf --reverse --height=50% --preview="ls -AF --color=always ${review_root}/{1}")"
    [ -z "${repo}" ] && return
    _wezterm_new_tab "${review_root}/${repo}" "${repo}(review)"
}

wr について補足すると、開発用とレビュー用でリポジトリのclone先ディレクトリを分けています。レビュー依頼が来たときに開発中のブランチを切り替える必要がなく、wr で即座にレビュー用のタブを開いてそのまま確認に入れます。


wgでインクリメンタルサーチしながらリポジトリを絞り込んでいる様子

ペイン = 同一リポジトリ内の並列セッション

ペインは同一リポジトリ内で Claude Code のセッションを新たに立ち上げるときに使います。1つのリポジトリに対して複数の Claude Code セッションを並列で走らせられます。

並列セッションで作業する際は、wtpgo install または brew でインストール可能)を使って git worktree を作成し、claude --worktree で worktree 内で Claude Code を動かしています。

  • wtp はブランチ名だけで worktree を作成できる CLI ツール(wtp add feature/new-auth のように簡潔に書ける)
  • .wtp.yml で post_create hook を定義でき、.env のコピーや依存パッケージのインストールなどのセットアップを自動化できる
  • wtp remove --with-branch で worktree とブランチをまとめて削除できる

これにより同一リポジトリで複数ブランチの作業を互いに干渉させずに並列実行できます。

# 1. worktree を作成
wtp add feature/new-auth

# 2. worktree 内で Claude Code を起動
claude --worktree feature/new-auth

# 3. セッション名をつける
/rename new-auth

複数セッションを立ち上げると「どのペインでどの作業をしていたか」がわからなくなりがちです。Claude Code を起動したらすぐに /rename でセッション名をつけるようにしています。セッション名は Claude Codeのプロンプト上に表示されるため、作業の切り替え時に迷わずに済みます。

WezTermの構成
作業ルートディレクトリごとにタブを分け、作業内容ごとにペインを追加しています

Claude Code から応答があったらタブにマーカーをつける

Claude Code からの応答があったらタブにマーカーをつけ、別タブにいても気づけるようにしました。これにより 複数タブで並列作業 が可能になります。

タブにマーカーを付ける
応答があったタブにはマーカーが付きます

まず Claude Code 側の設定です。NotificationStop のフックでスクリプトを実行し、その中で printf '\a'(bell)をターミナルに送信しています。

~/.claude/settings.json
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/notify.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/notify.sh"
          }
        ]
      }
    ]
  }
}
notify.sh
# WezTermタブにbell通知マーカーを付ける
printf '\a' > /dev/tty 2>/dev/null

次に WezTerm 側です。bell イベントを受け取ったタブのIDを記録します。

~/.config/wezterm/wezterm.lua
-- bellが鳴ったタブを追跡するテーブル
local bell_tabs = {}
wezterm.on("bell", function(window, pane)
  local tab = pane:tab()
  if tab then
    bell_tabs[tostring(tab:tab_id())] = true
  end
end)

仕組みはシンプルで、WezTerm の bell イベントを監視し、bell が鳴ったタブのIDを bell_tabs テーブルに記録しています。Claude Code はユーザーの入力を待つ際に bell を鳴らすため、これを応答完了の検知に利用できます。

タブタイトルの描画処理では、bell_tabs に記録があるタブに マーカーを表示し、そのタブをアクティブにしたタイミングでクリアします。

~/.config/wezterm/wezterm.lua
wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)
  local background = "#161821"
  local foreground = "#FFFFFF"

  local tab_id = tostring(tab.tab_id)
  if tab.is_active then
    background = "#e5c07b"
    foreground = "#161821"
    -- アクティブタブに切り替えたらbell状態をクリア
    bell_tabs[tab_id] = nil
  elseif bell_tabs[tab_id] then
    background = "#161821"
    foreground = "#e5c07b"
  end

  local title = tab.tab_title
  if not title or #title == 0 then
    local pane = tab.active_pane
    local cwd_uri = pane.current_working_dir
    if cwd_uri then
      local cwd = cwd_uri.file_path or tostring(cwd_uri)
      title = cwd:match("([^/]+)/?$") or cwd
    else
      title = pane.title
    end
  end

  local index = tab.tab_index + 1
  local marker = bell_tabs[tab_id] and "● " or ""
  title = "   " .. marker .. index .. ": " .. wezterm.truncate_right(title, max_width - 6) .. "   "

  return {
    { Background = { Color = background } },
    { Foreground = { Color = foreground } },
    { Text = title },
  }
end)

オーバーレイ表示で素早く確認・復帰

以下の記事を参考に、以下のツールをオーバーレイ表示するようにしました。
https://zenn.dev/soramarjr/articles/ff6d80f7b524d6

  • LazyGit — Git操作
  • Neovim — ファイル編集
  • Zsh — シェル操作
  • git diff — delta + diffnav を使用した差分確認

ちょっとした確認ならオーバーレイを開いてすぐ閉じ、プロンプトに戻れます。Claude Code での作業中に「ちょっとdiffを見たい」「ファイルを直接編集したい」といった場面で、セッションを離れずに済むのが便利です。

~/.local/bin/wlay
#!/bin/bash
# WezTerm overlay pane: split-pane + zoom-pane to cover the current pane
# Auto-closes on program exit → unzoom → original pane restored
#
# Usage:
#   wlay          → zsh
#   wlay sh       → zsh
#   wlay nvim [file] → nvim [file]
#   wlay git      → lazygit
#   wlay diff     → git diff | diffnav

set -euo pipefail

case "${1:-sh}" in
  sh)   set -- zsh ;;
  nvim) shift; set -- nvim "$@" ;;
  git)  shift; set -- lazygit "$@" ;;
  diff) shift; set -- zsh -c "git diff $* | diffnav" ;;
  *)    echo "Unknown command: $1" >&2; exit 1 ;;
esac

pane_id=$(wezterm cli split-pane --bottom --cwd "$(pwd)" -- "$@")
wezterm cli zoom-pane --zoom --pane-id "$pane_id"

ポイントは最後の2行です。split-pane で下にペインを作り、即座に zoom-pane でズームすることで、画面全体を覆うオーバーレイとして振る舞います。プログラムが終了するとペインが自動で閉じ、ズームも解除されて元のペインに戻ります。

使い方はシンプルで、例えば Claude Code のプロンプト上から ! wlay git と打てば LazyGit が全画面で開き、閉じればそのままプロンプトに復帰できます。

ソースコードの変更差分を見る際は deltadiffnav を活用し、視認性の高いDiffビューで差分をチェックしています。
https://github.com/dandavison/delta
https://github.com/dlvhdr/diffnav

Delta+Diffnav
特定のファイルの差分だけをグラフィカルに確認できます

まとめ

本記事で紹介した構成をまとめます。

この構成に変えてから、Claude Code の応答待ちの間に別の作業を進められるようになり、並列作業の効率が上がりました。

概念 役割 主な用途
タブ ディレクトリ単位の切り替え リポジトリ、レビュー、メモ等
ペイン 同一リポジトリ内の並列セッション wtp + claude --worktree
タブマーカー 応答検知による通知 並列作業時の完了検知
オーバーレイ 一時的なツール起動 LazyGit、Neovim、diff確認

私の場合、AIエージェントを中心に使うようになってから、いかにAIエージェントをマルチタスクに活用できるかが生産性に直結するようになりました。WezTerm は Lua で柔軟にカスタマイズできるので、自分の開発スタイルに合わせて構成を育てていけるのが魅力です。

一方で、過剰な並列作業は混乱して効率が下がるため、ターミナルをマルチタスク向けに最適化しても結局はどのように作業の優先度付けを行うかが重要であるとも感じています。

この記事が、ターミナル環境を見直すきっかけになれば嬉しいです。

Kurashiru Tech Blog

Discussion