Skip to content

Instantly share code, notes, and snippets.

@yousan
Last active May 1, 2026 05:07
Show Gist options
  • Select an option

  • Save yousan/a633bcbc9570fcd72a29384ecf792e8d to your computer and use it in GitHub Desktop.

Select an option

Save yousan/a633bcbc9570fcd72a29384ecf792e8d to your computer and use it in GitHub Desktop.
Claude Code 並行開発用 tmux セットアップ(複数インスタンスを並列で動かして別Issueを同時進行する)

Claude Code 並行開発 tmux セットアップ

同じプロジェクトを複数の Claude Code インスタンスで並列に走らせるための tmux セットアップ一式。 別ブランチで別 Issue を同時進行できる。

構成

~/myproj_base/
├── tmux.sh              # tmux セッション構築スクリプト
├── status.sh            # 全インスタンスの状態を表示するモニター
├── project_1/           # Claude インスタンス 1 の作業ディレクトリ
├── project_2/           # Claude インスタンス 2 の作業ディレクトリ
└── project_3/           # Claude インスタンス 3 の作業ディレクトリ

~/.tmux.conf             # tmux の常用設定(同梱の tmux.conf を参照)

project_N に同じリポジトリを git clone しておく。 tmux.conf~/.tmux.conf にコピーすると、tmux.sh 経由でないときも同じ操作感になる。

tmux ウィンドウ構成

Window 用途
1: manager 上ペイン: watch status.sh、下ペイン: 親 Claude
2: work-1 project_1 の Claude
3: work-2 project_2 の Claude
4: work-3 project_3 の Claude

使い方

  1. tmux.shstatus.sh の冒頭の変数を環境に合わせて書き換える
    • SESSION / TMUX_SESSION: tmux セッション名
    • BASE_DIR: 親ディレクトリ
    • PROJECT_PREFIX: 作業ディレクトリのプレフィックス
    • INSTANCE_COUNT: 並列数
    • GH_REPO: GitHub Issue 連携を使う場合のリポジトリ名(任意)
  2. chmod +x tmux.sh status.sh
  3. ./tmux.sh で起動。再実行すれば既存セッションに復帰する

status.sh の機能

  • 各インスタンスの現在ブランチ・差分数・Vite/API ポートの起動状態を表示
  • ブランチ名から Issue 番号を自動検出(feature/137-xxx#137
  • gh コマンドで Issue タイトルを取得(5分キャッシュ)
  • tmux ペインから Claude の状態(作業中 / 入力待ち / plan待ち)を判定
    • スピナー文字に依存せず、↓ N tokens 表示で処理中を判定するため安定
  • tmux ウィンドウタイトルを 1-#137 のように動的に更新
  • STATUS.md を自動生成

並列実行のためのポート割り当て例(参考)

開発サーバー(Vite)と DB(Supabase 等)のポートをずらしておく:

サービス N=1 N=2 N=3
Vite 5203 5204 5205
API 30021 31021 32021
DB 30022 31022 32022

各リポジトリの vite.config.ts や Supabase 設定を N に応じて書き換える。

ライセンス

MIT

#!/bin/bash
###########################################################################
# Claude Code 並行開発 ステータスモニター
# 使い方: watch -n 5 -c ./status.sh
#
# git / tmux の状態から自動検出し、STATUS.md も自動生成する。
# Claude インスタンスの自己申告に依存しない。
###########################################################################
# ▼ あなたの環境に合わせて書き換える
BASE_DIR="$HOME/myproj_base"
TMUX_SESSION="myproj"
PROJECT_PREFIX="project" # 作業ディレクトリ名のプレフィックス
INSTANCE_COUNT=3
GH_REPO="YOUR_ORG/YOUR_REPO" # GitHub Issue タイトル取得用(不要なら空文字でOK)
STATUS_FILE="$BASE_DIR/STATUS.md"
# ▼ Vite/API ポート(並列起動用にずらす)
get_ports() {
local n="$1"
case $n in
1) echo "5203 30021" ;;
2) echo "5204 31021" ;;
3) echo "5205 32021" ;;
*) echo "$((5202 + n)) $((30021 + (n - 1) * 1000))" ;;
esac
}
# 色定義
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
# --- ブランチ名から Issue 番号を推測 ---
extract_issue_number() {
local branch="$1"
local num
num=$(echo "$branch" | grep -oP '(?<![0-9])\d{1,4}(?![0-9])' | head -1)
if [ -n "$num" ] && [ "$num" -gt 0 ] 2>/dev/null; then
echo "$num"
else
echo ""
fi
}
# --- GitHub Issue のタイトルを取得(5分キャッシュ) ---
ISSUE_CACHE_DIR="/tmp/cc-status-issue-cache"
mkdir -p "$ISSUE_CACHE_DIR" 2>/dev/null
fetch_issue_title() {
local num="$1"
[ -z "$num" ] || [ -z "$GH_REPO" ] && return
local cache_file="$ISSUE_CACHE_DIR/$num"
if [ -f "$cache_file" ] && [ "$(find "$cache_file" -mmin -5 2>/dev/null)" ]; then
cat "$cache_file"
return
fi
local title
title=$(gh issue view "$num" --repo "$GH_REPO" --json title --jq .title 2>/dev/null)
if [ -n "$title" ]; then
echo "$title" > "$cache_file"
echo "$title"
fi
}
# --- tmux ペインから Claude の状態を検出 ---
# Claude Code のスピナー表示中に出る "↓ N tokens" を見て処理中を判定する。
# スピナー文字(✻ / ✶ / *)に依存しないため安定。
detect_claude_state() {
local win_index="$1"
[ -z "$win_index" ] && echo "?" && return
local pane_text
pane_text=$(tmux capture-pane -t "${TMUX_SESSION}:${win_index}" -p 2>/dev/null)
if [ -z "$pane_text" ]; then
echo "?"
return
fi
local last_lines
last_lines=$(echo "$pane_text" | tail -5)
# 「↓ N tokens」が含まれる行 = スピナー表示中(処理中)
if echo "$pane_text" | grep -qP '↓\s*[\d.]+k?\s*tokens'; then
echo "作業中"
elif echo "$last_lines" | grep -q "INSERT"; then
if echo "$last_lines" | grep -q "plan mode"; then
echo "plan待ち"
else
echo "入力待ち"
fi
elif echo "$last_lines" | grep -q "Try \"how does"; then
echo "未指示"
elif echo "$last_lines" | grep -q '^\$\|^❯'; then
echo "shell"
else
echo "?"
fi
}
# =======================================================================
# メイン処理
# =======================================================================
echo -e "${BOLD}並行開発${NC} ${DIM}$(date '+%H:%M:%S')${NC}"
# ss 結果をキャッシュ(ループ内で毎回呼ばない)
SS_CACHE=$(ss -tlnp 2>/dev/null)
MD_ROWS=""
for N in $(seq 1 "$INSTANCE_COUNT"); do
DIR="$BASE_DIR/${PROJECT_PREFIX}_$N"
[ ! -d "$DIR" ] && continue
# git 情報
BRANCH=$(git -C "$DIR" branch --show-current 2>/dev/null)
DIRTY=$(git -C "$DIR" status --porcelain 2>/dev/null | wc -l)
# ポート
read VITE API <<< "$(get_ports "$N")"
# サービス起動確認
echo "$SS_CACHE" | grep -q ":$VITE " && V="${GREEN}V${NC}" || V="${DIM}-${NC}"
echo "$SS_CACHE" | grep -q ":$VITE " && V_MD="V" || V_MD="-"
echo "$SS_CACHE" | grep -q ":$API " && S="${GREEN}S${NC}" || S="${DIM}-${NC}"
echo "$SS_CACHE" | grep -q ":$API " && S_MD="S" || S_MD="-"
# Issue 番号(ブランチ名から自動検出)
ISSUE_NUM=$(extract_issue_number "$BRANCH")
ISSUE_TITLE=""
[ -n "$ISSUE_NUM" ] && ISSUE_TITLE=$(fetch_issue_title "$ISSUE_NUM")
# Claude 状態(ウィンドウインデックス N+1 で参照: manager=1, work-1=2, ...)
WIN_IDX=$((N + 1))
CLAUDE=$(detect_claude_state "$WIN_IDX")
# tmux ウィンドウタイトル更新
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
if [ -n "$ISSUE_NUM" ]; then
tmux rename-window -t "$TMUX_SESSION:$WIN_IDX" "${N}-#${ISSUE_NUM}" 2>/dev/null
else
tmux rename-window -t "$TMUX_SESSION:$WIN_IDX" "${N}" 2>/dev/null
fi
fi
# --- コンパクト表示(2行) ---
ISSUE_DISPLAY=""
if [ -n "$ISSUE_NUM" ]; then
TITLE_SHORT=$(python3 -c "import sys; s=sys.argv[1]; print(s[:20]+'…' if len(s)>20 else s)" "$ISSUE_TITLE" 2>/dev/null || echo "$ISSUE_TITLE")
ISSUE_DISPLAY="${BOLD}#${ISSUE_NUM}${NC} ${TITLE_SHORT}"
fi
if [ "$CLAUDE" = "作業中" ]; then
ST="${GREEN}作業中${NC}"
elif [ "$CLAUDE" = "入力待ち" ] || [ "$CLAUDE" = "plan待ち" ]; then
ST="${YELLOW}${CLAUDE}${NC}"
elif [ "$CLAUDE" = "未指示" ] || [ "$CLAUDE" = "shell" ]; then
ST="${DIM}待機${NC}"
else
ST="${DIM}${CLAUDE}${NC}"
fi
echo -e "${CYAN}[${N}]${NC} ${ISSUE_DISPLAY:-${YELLOW}${BRANCH}${NC}} ${ST}"
echo -e " ${DIM}${BRANCH}${NC} Δ${DIRTY} ${V}${S} ${DIM}claude:${NC}${CLAUDE}"
# STATUS.md 行
ISSUE_MD="-"
if [ -n "$ISSUE_NUM" ] && [ -n "$GH_REPO" ]; then
ISSUE_MD="[#${ISSUE_NUM}](https://github.com/$GH_REPO/issues/$ISSUE_NUM) ${ISSUE_TITLE}"
elif [ -n "$ISSUE_NUM" ]; then
ISSUE_MD="#${ISSUE_NUM} ${ISSUE_TITLE}"
fi
MD_ROWS="${MD_ROWS}| ${N} | ${ISSUE_MD} | ${BRANCH} | ${CLAUDE} | ${V_MD}${S_MD} Δ${DIRTY} |\n"
done
# STATUS.md 自動生成
cat > "$STATUS_FILE" <<EOF
| # | Issue | ブランチ | Claude | メモ |
|---|-------|----------|--------|------|
$(echo -e "$MD_ROWS" | sed '/^$/d')
_$(date '+%H:%M:%S')_
EOF
###########################################################################
# ~/.tmux.conf (並行開発用 Claude Code 環境向けの抜粋)
#
# tmux.sh の中でも `tmux set -g ...` で同等の設定をしているが、
# tmux 自体を素で起動した時にも常に効かせたい設定はここに置く。
#
# 反映: tmux 起動中に Ctrl+t → r (または `tmux source-file ~/.tmux.conf`)
###########################################################################
# --- ターミナル ---
# 256色 + truecolor を有効化(Claude Code の色表示が綺麗になる)
set -g default-terminal "tmux-256color"
set -as terminal-overrides ',*:Tc'
# --- プリフィックス変更 ---
# Ctrl+b は Emacs/vim/readline の「カーソル左移動」と衝突するので Ctrl+t に変える
set -g prefix C-t
unbind C-b
bind C-t send-prefix
# --- ベル ---
# tmux + SSH + iTerm2 環境で Claude Code の stop hook(echo -e '\a')を
# 通知音として受け取るには "any" にする。
# iTerm2 側は Preferences > Profiles > Terminal > Notifications で
# "Audible bell" を有効にすること。
# none/any/current/other から選択(デフォルトは none)。
set-option -g bell-action any
# --- ウィンドウ ---
# ウィンドウ番号を 1 始まりに(0 は遠いので使いにくい)
set-option -g base-index 1
# ウィンドウの自動リネーム off(status.sh から動的に設定するため)
set-window-option -g automatic-rename off
# バックグラウンドウィンドウで動作があったらステータスラインでハイライト
set-window-option -g monitor-activity on
# --- コピーモード ---
# Emacs キーバインド(vi バインドが好きなら "vi" に変える)
set -g mode-keys emacs
# --- ペイン分割(覚えやすいキーに) ---
bind 2 split-window -v # Ctrl+t → 2 で上下分割
bind 4 split-window -h # Ctrl+t → 4 で左右分割
bind k kill-pane # Ctrl+t → k でペインを閉じる
# --- 履歴 / マウス ---
set -g history-limit 100000
set -g mouse on # マウスでスクロール・ペイン選択ができる
# --- 設定の再読み込み ---
bind r source-file ~/.tmux.conf \; display-message "Reloaded config !!"
#!/bin/bash
###########################################################################
# Claude Code 並行開発用 tmux セットアップ
# -------------------------------------------------------------------------
# 同じプロジェクトを N 個並列に走らせるための tmux セッション構築スクリプト。
# "PROJECT" セッションを探し、あれば復帰、なければウィンドウ構成ごと自動生成。
#
# ウィンドウ構成(例):
# 1: manager — 管理用(上: ステータスモニター / 下: 親 Claude セッション)
# 2: work-1 — project_1(Claude インスタンス 1)
# 3: work-2 — project_2(Claude インスタンス 2)
# 4: work-3 — project_3(Claude インスタンス 3)
#
# 何度呼び出しても同じセッションに復帰できる。
###########################################################################
# 基本操作まとめ
# -------------------------------------------------------------------------
# このスクリプトは tmux のプリフィックスを Ctrl+b → Ctrl+t に変更する。
# (Ctrl+b は Emacs 系・vim・readline の「カーソル左移動」と衝突するため)
#
# ■ デタッチ(tmux から抜ける)
# Ctrl+t → d
#
# ■ ウィンドウ操作
# Ctrl+t → c (新しいウィンドウを作る)
# Ctrl+t → n (次のウィンドウへ)
# Ctrl+t → p (前のウィンドウへ)
# Ctrl+t → 0..9 (番号でジャンプ)
# Ctrl+t → w (一覧から選ぶ)
# Ctrl+t → , (現在のウィンドウ名をリネーム)
# Ctrl+t → & (現在のウィンドウを閉じる)
#
# ■ ペイン操作
# Ctrl+t → " (上下分割)
# Ctrl+t → % (左右分割)
# Ctrl+t → ↑↓←→ (ペイン移動)
# Ctrl+t → o (次のペインへ)
# Ctrl+t → x (現在のペインを閉じる)
# Ctrl+t → z (ペインを最大化/戻す)
# Ctrl+t → space(レイアウト切替)
#
# ■ コピーモード(スクロール / 検索 / コピー)
# Ctrl+t → [ (コピーモード開始。q で終了)
# コピーモード中: ↑↓ でスクロール、/ で検索、space で選択、Enter でコピー
#
# ■ セッション操作
# Ctrl+t → s (セッション一覧)
# Ctrl+t → $ (現在のセッションをリネーム)
#
# ■ デタッチした後の再アタッチ
# tmux ls (セッション一覧)
# tmux attach -t NAME
###########################################################################
# ▼ あなたの環境に合わせて書き換える
SESSION="myproj" # tmux セッション名
BASE_DIR="$HOME/myproj_base" # 親ディレクトリ
PROJECT_PREFIX="project" # 作業ディレクトリ名のプレフィックス("project_1", "project_2" になる)
INSTANCE_COUNT=3 # 並列インスタンス数
# ▼ セッションが無ければ作る
if ! tmux has-session -t "$SESSION" 2>/dev/null; then
# --- manager ウィンドウ(上下分割) ---
tmux new-session -d -s "$SESSION" -n manager -c "$BASE_DIR"
# tmux 設定(セッション作成後に設定する。サーバー未起動だと set -g が失敗するため)
tmux set-option -g history-limit 50000
tmux set -g mouse off
# プリフィックスを Ctrl+b → Ctrl+t に変更
tmux set -g prefix C-t
tmux unbind C-b
tmux bind C-t send-prefix
# タイトル自動リネームを無効化(status.sh から動的に設定するため)
tmux set -g allow-rename off
tmux set -g automatic-rename off
# 上ペイン: ステータスモニター
tmux send-keys -t "$SESSION:manager" "watch -n 5 -c $BASE_DIR/status.sh" C-m
# 下ペインを作成: 管理用 Claude セッション
tmux split-window -t "$SESSION:manager" -v -c "$BASE_DIR"
tmux send-keys -t "$SESSION:manager.1" "claude --continue" C-m
# 上ペインを小さめに(ステータス表示用なので 40% 程度)
tmux resize-pane -t "$SESSION:manager.0" -y 40%
# --- 作業ウィンドウ(必ず INSTANCE_COUNT 個作成) ---
for N in $(seq 1 "$INSTANCE_COUNT"); do
DIR="$BASE_DIR/${PROJECT_PREFIX}_$N"
WINDOW="work-$N"
if [ ! -d "$DIR" ]; then
echo "Creating $DIR ..."
mkdir -p "$DIR"
fi
tmux new-window -t "$SESSION:" -n "$WINDOW" -c "$DIR"
tmux send-keys -t "$SESSION:$WINDOW" "claude --continue" C-m
done
# manager ウィンドウの下ペイン(Claude)を初期表示にする
tmux select-window -t "$SESSION:manager"
tmux select-pane -t "$SESSION:manager.1"
fi
tmux attach -t "$SESSION"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment