1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeで複数Agentを並列に動かしたら作業ディレクトリがぐちゃぐちゃになった話

1
Posted at

Claude Code Agent Viewとgit worktreeで、Issue実装とPRレビューを並列に走らせる

Claude Code Agent Viewを使い始めて最初に直面したのが「複数Agentを動かすと作業ディレクトリがぐちゃぐちゃになる問題」だった。Issue実装、PRレビュー、テスト修正を同じディレクトリで並列に回すと、未commit差分とcheckoutが混線して数十分単位の手戻りが出る。

結論としては、Agent Viewを管制塔、git worktreeを作業領域の分離機構として使い分ける運用に落ち着いた。さらにOpusとSonnetをタスクで分業させることで、コスト・速度・品質のバランスが安定した。

この記事では、その運用を支えるためにシェルスクリプト2本(issue-agent / pr-review-agent)を作った話と、実装中にハマったポイントをまとめる。


なぜworktreeで分離するのか

Agent Viewはセッションを束ねてくれるが、ファイルシステム上の作業領域までは分けてくれない。

Agent A: Issue #123 の実装中
Agent B: PR #456 のレビュー中
Agent C: テスト修正中

これを同じ作業ディレクトリで動かすと、片方のAgentが他方のbranchにcheckoutして差分が消える、生成ファイルが混ざる、といった事故が起きる。

そこで作業単位ごとにworktreeを切る。

~/my-repo                 # main working copy
~/issue-123-some-task     # Issue実装用worktree
~/review-pr-456           # PRレビュー用worktree

これで各Agentの作業領域が完全に独立する。Agent Viewで見ているのはあくまでセッションであって、実体のディレクトリは別物。この役割分担を意識するかしないかで、並列運用の安定度が大きく変わる。


モデル分業: Opusで計画、Sonnetで実装

Issue実装で最初にやったのが「Opusに全部任せる」だった。設計の質は高いが、実装まで走らせるとコストが膨らみ、応答も遅い。

逆にSonnetで最初から実装させると、設計判断が甘い場面で手戻りが出る。

落ち着いたのは以下の分担。

Opus:   実装プランだけ作る
人間:   プランを確認する
Sonnet: 実装する

ここで重要なのが、Opusで設計させた同じセッションで「じゃあ実装して」と続けないこと。同じセッション内で続けるとそのままOpusで実装に入ってしまう。意図せずコストが跳ねる典型パターン。

そのためスクリプト側でClaudeプロセスを明示的に分けている。

claude --model "$PLAN_MODEL" "$PLAN_PROMPT"

# 人間がプラン確認

claude --model "$IMPL_MODEL" "$IMPL_PROMPT"

なお、モデル名は opus-4.7 のようなバージョン付き指定だと環境によって弾かれる。

There's an issue with the selected model (opus-4.7).
It may not exist or you may not have access to it.

aliasのほうが安全なので、環境変数で渡している。

export CLAUDE_PLAN_MODEL="opus"
export CLAUDE_IMPL_MODEL="sonnet"
export CLAUDE_REVIEW_MODEL="sonnet"

issue-agent: Issue実装を半自動化する

Issue番号を渡すと、branch作成 → worktree作成 → Opusで計画 → 人間確認 → Sonnetで実装 → push → PR作成、までを一気通貫で進めるスクリプト。

#!/usr/bin/env bash
set -euo pipefail

# Usage:
#   issue-agent 123
#   issue-agent 123 some-task
#   issue-agent 123 --bg
#   issue-agent 123 --no-pr

ROOT="${AI_DEV_ROOT:-$HOME/my-repo}"
WORKTREE_PARENT="${WORKTREE_PARENT:-$HOME}"
BASE_BRANCH="${BASE_BRANCH:-main}"
PLAN_MODEL="${CLAUDE_PLAN_MODEL:-opus}"
IMPL_MODEL="${CLAUDE_IMPL_MODEL:-sonnet}"

ISSUE="$1"; shift || true
BG=0; CREATE_PR=1; SLUG=""

for arg in "$@"; do
  case "$arg" in
    --bg) BG=1 ;;
    --no-pr) CREATE_PR=0 ;;
    *) SLUG="$arg" ;;
  esac
done

cd "$ROOT"
git fetch origin --prune

TITLE="$(gh issue view "$ISSUE" --json title --jq '.title' 2>/dev/null || true)"

slugify() {
  echo "$1" | tr '[:upper:]' '[:lower:]' \
    | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g' | cut -c1-50
}

[[ -z "$SLUG" ]] && SLUG="$(slugify "${TITLE:-issue-$ISSUE}")"
[[ -z "$SLUG" ]] && SLUG="issue-$ISSUE"

BRANCH="feature/issue-${ISSUE}-${SLUG}"
WORKTREE="${WORKTREE_PARENT}/issue-${ISSUE}-${SLUG}"

if [[ ! -d "$WORKTREE/.git" && ! -f "$WORKTREE/.git" ]]; then
  if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
    git worktree add "$WORKTREE" "$BRANCH"
  else
    git worktree add -b "$BRANCH" "$WORKTREE" "origin/$BASE_BRANCH"
  fi
fi

cd "$WORKTREE"
mkdir -p docs/agent-plans
PLAN_FILE="docs/agent-plans/issue-${ISSUE}-plan.md"

PLAN_PROMPT="Issue #$ISSUE の実装プランだけを $PLAN_FILE に作成してください。
コード変更はまだしないでください。
含める内容: 目的 / 現状把握 / 変更対象ファイル / 実装ステップ /
非対象スコープ / テスト方針 / リスク / Sonnet向け実装指示 / PR説明案"

IMPL_PROMPT="Issue #$ISSUE を実装してください。
最初に必ず $PLAN_FILE と CLAUDE.md を読み、方針に従ってください。
スコープ外変更は避け、変更後に関連テストを実行してください。
問題なければ次の形式でcommit:
  Implement issue #$ISSUE: ${TITLE:-$SLUG}"

# Step 1: Plan
claude --model "$PLAN_MODEL" "$PLAN_PROMPT"

[[ ! -f "$PLAN_FILE" ]] && { echo "Plan file not found."; exit 1; }

read -r -p "Start Sonnet implementation? [y/N] " ANSWER
[[ ! "$ANSWER" =~ ^(y|Y|yes|YES)$ ]] && exit 0

# Step 2: Implementation
if [[ "$BG" -eq 1 ]]; then
  BG_PROMPT="$IMPL_PROMPT
追加: 完了後 git add -A && git commit && git push -u origin $BRANCH
そして gh pr create --fill --base $BASE_BRANCH --head $BRANCH を実行。"
  claude --model "$IMPL_MODEL" --bg "$BG_PROMPT"
  exit 0
fi

claude --model "$IMPL_MODEL" "$IMPL_PROMPT"

# Step 3: PR作成
[[ "$CREATE_PR" -eq 0 ]] && exit 0
[[ -n "$(git status --porcelain)" ]] && { echo "Uncommitted changes."; exit 1; }

git push -u origin "$BRANCH"
PR_URL="$(gh pr create --fill --base "$BASE_BRANCH" --head "$BRANCH")"
echo "PR created: $PR_URL"

実体験で効いたのは、プラン生成と実装の間に人間の確認ステップを必ず挟むこと。Opusが書いたプランを docs/agent-plans/issue-NNN-plan.md に保存し、preview表示してから「実装に進むか?」を聞く。ここでスコープのズレに気づければ、Sonnetでの実装が安定する。

--bg モードはAgent Viewでの並列監視に便利だが、ひとつ罠がある。claude --bg はセッションを作ってすぐ戻ってくるため、シェルスクリプトからは完了を待てない。つまり「実装完了を待ってPR作成」というフローはシェル側で組めない。--bg を使う場合はClaude本人にcommit/push/PR作成まで指示する必要がある。PR作成までシェル側で制御したいなら非--bgが安全。


pr-review-agent: PRレビュー専用worktreeを作る

PR番号を渡すと、PR headを取得 → レビュー用worktree作成 → .claude 同期 → Sonnetでレビュー起動、を行うスクリプト。

#!/usr/bin/env bash
set -euo pipefail

# Usage: pr-review-agent 456 [--bg]

ROOT="${AI_DEV_ROOT:-$HOME/my-repo}"
WORKTREE_PARENT="${WORKTREE_PARENT:-$HOME}"
REVIEW_MODEL="${CLAUDE_REVIEW_MODEL:-sonnet}"

PR="$1"; shift || true
BG=0
for arg in "$@"; do [[ "$arg" == "--bg" ]] && BG=1; done

cd "$ROOT"
git fetch origin --prune

BRANCH="review/pr-${PR}"
WORKTREE="${WORKTREE_PARENT}/review-pr-${PR}"

sync_claude_tools() {
  if [[ -d "$ROOT/.claude" ]]; then
    rsync -a --exclude 'tmp/' "$ROOT/.claude/" "$WORKTREE/.claude/"
  fi
}

if [[ -d "$WORKTREE/.git" || -f "$WORKTREE/.git" ]]; then
  cd "$WORKTREE"
  git fetch origin "pull/${PR}/head"
  git checkout "$BRANCH" >/dev/null 2>&1 || true
  git reset --hard FETCH_HEAD
  sync_claude_tools
else
  git show-ref --verify --quiet "refs/heads/$BRANCH" && git branch -D "$BRANCH"
  git fetch origin "pull/${PR}/head:${BRANCH}"
  git worktree add "$WORKTREE" "$BRANCH"
  cd "$WORKTREE"
  sync_claude_tools
fi

PROMPT="/review:pr $PR"

if [[ "$BG" -eq 1 ]]; then
  claude --model "$REVIEW_MODEL" --bg "$PROMPT"
else
  claude --model "$REVIEW_MODEL" "$PROMPT"
fi

このスクリプトで一番ハマったのが .claude の同期。最初は同期処理なしで動かしていたが、レビューを起動すると以下のエラーが出た。

Unknown command: /review:pr. Did you mean /review-pr?

原因は、PR headのbranchに .claude/commands/review/pr.md が含まれていなかったこと。自分が普段のmain working copyに置いている自作commandやskillは、当然ながらレビュー対象branchには存在しない。レビュー対象が古いbranchから派生していればなおさら。

そこでworktree作成後に rsync でmain working copy側の .claude をコピーする運用にした。

rsync -a --exclude 'tmp/' "$ROOT/.claude/" "$WORKTREE/.claude/"

これでレビュー対象branchが古くても、自分が育てたレビュー用command/skillを使える。注意点として、レビュー対象PR自体が .claude 配下を変更している場合は上書きされてしまうので、その場合はレビュー用commandを ~/.claude/commands のような個人ディレクトリに置く選択肢もある。


日々の使い方

Issue実装を始める。

issue-agent 123

Agent Viewに投げて並列化する。

issue-agent 123 --bg
pr-review-agent 456 --bg
pr-review-agent 789 --bg

監視と操作。

claude agents                    # 一覧
claude logs <session_id>         # ログを見る
claude attach <session_id>       # セッションに入る
git worktree list                # worktree一覧
git worktree prune               # 不要なworktree情報を掃除

Issue #123を実装させながら、PR #456と#789のレビューを別Agentで並走させる、というのが実際の使い方。Agent Viewでセッションを切り替えながら、各worktreeの差分を別々に進められる。


並列化で効いた4つの設計判断

複数Agent運用を始めて分かったのは、並列化の本質はプロンプトの工夫よりも作業領域の分離にあるということ。

設計判断 効果
worktreeで作業ディレクトリを分離 差分混線・誤checkoutを防ぐ
Opus計画 → 人間確認 → Sonnet実装 コスト最適化と品質確保の両立
Claudeプロセスを明示的に分割 Opusのまま実装に入る事故を防ぐ
.claude をレビュー用worktreeに同期 古いbranchでも自作commandが使える

Claude Codeをただの「賢いチャット」から「並列稼働する複数Agent」に格上げするのは、結局のところファイルシステムとプロセス境界の設計だった。Agent Viewとgit worktreeの組み合わせは、その境界をきれいに引くための道具として今のところベストだと思っている。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?