Claude Codeのセッションを2つ3つ同時に開いて、別々のタスクを進めることが増えました。

このサイトも姉妹ブログも「1記事=1ブランチ=1PR」で運用しているので、タスクごとにブランチを切ります。ところが、2つ目のタスクを頼んだとき、前のタスクのブランチにそのままコミットが乗っていたということが何度も起きました。

最初のタスクでは「ブランチを切って」と言っています。次のタスクでは言い忘れます。すると次のタスクは、いま checkout されているブランチで淡々と作業を始めます。

この記事では、原因と git worktree での解決策をまとめます。VS Codeでの運用と、言い忘れても止まるように hooks で保険をかける設定も書きます。


結論:3行で

  • 原因は、セッションがディレクトリ単位なのに、ブランチはディレクトリに1本しか checkout できないこと
  • 解決策は git worktree。タスクごとに作業ディレクトリを分ければ、セッション=ディレクトリ=ブランチが1対1になる
  • VS Code拡張ならworktree ごとにウィンドウを開く。保険として hooks で main 上の編集を止める

何が起きていたか

流れはこうです。

  1. セッションAで「記事Xを書いて」と頼む。Claudeが article/x を切って書き始める
  2. その間にセッションBを開き、「リンク切れを直して」と頼む
  3. セッションBはそのとき checkout されている article/x の上でリンクを直し、コミットする
  4. 記事XのPRに、関係ないリンク修正が混ざる

気づくのはPRを作る段階か、レビューのときです。そこから、混ざったコミットを切り分ける後始末が始まります。Claudeに頼んだ作業は放っておけば終わりますが、後始末は手作業です。

姉妹ブログのリポジトリの履歴を見返すと、9月19日から20日の2日間で3本のブランチを行き来していました。1つの作業ディレクトリの中でです。片方の作業ブランチには、途中で main を2回取り込んだ痕跡も残っています。

ブランチまわりの事故は、これが初めてではありません。Claude Codeのレビュー記事に書いた、積み上げたPRのベースが main に戻らず本番に出なかった件も同じ系統です。どのブランチの上で作業しているかを、人間が把握しきれていないのが共通点でした。


原因は「セッション」と「ブランチ」の単位が違うこと

最初は「ブランチを切れ、と毎回言えばいい」と思っていました。ですが、それでは言い忘れた時点で終わりです。仕組みとして成立していないのが問題でした。

Claude Codeのセッションは、起動したディレクトリに紐づいています。セッションの履歴は ~/.claude/projects/ の下にディレクトリごとに保存されます(公式ドキュメント)。

一方でGitのブランチは、作業ディレクトリ1つにつき1本しか checkout できません。

セッションを2つ開いても、作業ツリーは1つ セッションA セッションB 作業ディレクトリ(1つ) checkout 中のブランチ: article/x

セッションが何個あっても、checkout 中のブランチは1つ

つまり、セッションを何個開いても、全部が同じ作業ツリーの同じブランチを見ています。セッションAがブランチを切り替えれば、セッションBも巻き込まれます。

「セッションごとにブランチを持たせる」という運用は、同じディレクトリを使う限り成り立ちません。ブランチを分けたいなら、ディレクトリを分けるしかない。

書いている途中で、もう1回やった

白状しておくと、この記事の下書きを任せたセッション自体が、意図していたのとは別のリポジトリで記事を書き、PRまで出していました。セッションを起動したディレクトリがリポジトリの外で、執筆用スキルの既定の投稿先が姉妹ブログになっていたためです。気づいてPRを閉じ、こちらのリポジトリで出し直しています。

セッションが「今どこにいるか」は、自分が思っているより曖昧でした。ブランチどころか、リポジトリごと違うことすらあります。


解決策:git worktree で作業ディレクトリごと分ける

git worktree は、1つのリポジトリから複数の作業ディレクトリを checkout するGitの標準機能です(git-worktree ドキュメント)。clone と違って .git の履歴は共有されるので、ディスクも時間もほとんど食いません。

Claude Codeはこれを前提にした起動オプションを持っています(公式: Worktrees、2026年9月30日時点)。

# ターミナル1
claude --worktree feature-x

# ターミナル2
claude --worktree fix-links

--worktree(短縮形は -w)を付けて起動すると、今いるコミットを起点に、次のことが自動で行われます。

項目 内容
作成場所 リポジトリ直下の .claude/worktrees/<名前>/
ブランチ名 worktree-<名前> が新規作成される
終了時 名前を付けずに作り、かつ変更がなければ自動削除。それ以外は残すか確認される

セッションはこの新しいディレクトリに紐づくので、セッション=ディレクトリ=ブランチが1対1になります。もう片方のセッションが何をしていても、こちらのブランチは動きません。

ブランチ名を自分の規約に合わせたい場合

このサイトは article/xxx という命名で運用しているので、worktree- で始まる名前は少し据わりが悪いです。

その場合はGitのコマンドで自分で作ってから、そのディレクトリでClaude Codeを起動します。

# origin/main から article/x ブランチを切り、専用ディレクトリに checkout する
git worktree add -b article/x .claude/worktrees/x origin/main

cd .claude/worktrees/x
claude

置き場所を .claude/worktrees/ に揃えると、Claude Codeが作ったものと並んで見通しがよくなります。ただし削除は自分でやります。.gitignore に .claude/worktrees/ を追加するのも忘れずに(この記事のPRで追加しています)。

実際にやって引っかかったこと

この記事自体、上のコマンドで作った worktree の中で書いています。やってみて引っかかったのは1点だけでした。

node_modules は worktree ごとに必要です。Gitが追跡していないファイルは checkout の対象外なので、npm run dev を動かすには worktree の中で npm ci をやり直す必要がありました。

作業が終わってPRをマージしたら、worktree とブランチを消します。

git worktree remove .claude/worktrees/x
git branch -d article/x
git worktree list   # 残っているものを確認

VS Codeで使う場合はどうするか

私は普段、VS Codeの拡張機能からClaude Codeを使っています。ここが一番悩んだところです。

VS Codeの拡張機能は、開いているワークスペースのルートを作業ディレクトリとして使います(公式: VS Code)。タブを何枚開いても、全部が同じワークスペース、つまり同じ作業ツリーです。前述の「セッションを何個開いても同じブランチ」が、そのまま起きます。

採ることにしたのは、1つのVS Codeウィンドウに1つの worktreeという運用です。

# worktree を作ってから、そのディレクトリを別ウィンドウで開く
git worktree add -b article/x .claude/worktrees/x origin/main
code .claude/worktrees/x

新しいウィンドウで拡張機能を開けば、そのセッションは worktree のディレクトリに紐づくはずです。拡張はワークスペースのルートを作業ディレクトリにするからです。ウィンドウのタイトルにディレクトリ名が出るので、どのウィンドウがどの作業かは見ればわかります。

他の選択肢も並べておきます。

方法 向いている場面
worktree ごとにウィンドウ(採用) 拡張機能のチャットUIを使いたい
統合ターミナルで claude -w CLI操作に慣れている。CLIを別途インストールする必要あり
デスクトップアプリ セッション開始時に worktree を選ぶUIがある

統合ターミナルで claude -w を使う場合、VS Codeのエクスプローラが見ているのは元の作業ツリーのままです。Claudeが編集しているファイルと、自分が開いているファイルがずれるので注意が要ります。

デスクトップアプリには、セッションを始めるときに worktree を選ぶ項目があります。拡張機能側には、公式ドキュメントを見る限り(2026年9月30日時点)同じ項目は見当たりませんでした。なので拡張機能で使うなら、先に worktree を作って、そこをウィンドウで開くのが現実的です。

なお、VS Codeには Claude Code の入口が2つあります。この記事で扱っているのは Anthropic 公式拡張の方です。もう1つの入口についてはClaude直接契約 vs GitHub Copilotで書いています。


保険:言い忘れても止まるように hooks を入れる

worktree を使えば混ざりません。ただ、worktree を作るのを忘れて、いつものディレクトリでセッションを開くことはあります。

そのための保険として、Claude Codeの hooks を2つ入れました。hooks は、決まったタイミングで任意のコマンドを走らせる仕組みです(公式: Hooks)。

1. セッション開始時に「今どのブランチか」を見せる

SessionStart フックの標準出力は、そのままClaudeの文脈に入ります。ブランチ名と、ブランチ運用のルールを毎回渡します。

#!/bin/bash
# .claude/hooks/session-start.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "not-a-git-repo")
echo "現在の git ブランチ: ${BRANCH}"
echo "ルール: main/master 上ではファイルを編集しない。タスク開始時は main から新しいブランチを切る。現在のブランチが別タスクのものなら、作業前にユーザーへ確認する。"

2. main 上での編集をブロックする

PreToolUse フックは、ツールが実行される直前に走ります。終了コード2で返すと、そのツール呼び出しは止まり、標準エラー出力がClaudeに理由として渡ります。

#!/bin/bash
# .claude/hooks/block-main-edits.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
  echo "ブロック: ${BRANCH} 上ではファイルを編集できません。先に作業ブランチを切ってください。" >&2
  exit 2
fi
exit 0

settings.json への登録

2つのスクリプトを .claude/settings.json に登録します。

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start.sh" }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-main-edits.sh" }
        ]
      }
    ]
  }
}

止めているのは Edit と Write だけです。Bash 経由の書き込み(sed -i やリダイレクト)は止まりません。あくまでファイル編集ツールに対する保険です。

動かして確かめた

使い捨てのリポジトリで動作を確認しました。結果は次のとおりです。

状況 結果
main 上で「hello.txt を作って」 ブロックされ、ファイルは作られない。Claudeは理由をそのまま返してくる
作業ブランチ上で同じ指示 普通に作られる

もう1つ、想定していなかった動きがありました。最初に試したとき、Claudeは編集の前に自分でブランチを切ってからファイルを作りました。SessionStart で渡した「main 上では編集しない」というルールを読んで、先にブランチを作成したのです。

つまり SessionStart のルールだけでも効くことはあります。ただ、毎回そうしてくれる保証はないので、PreToolUse のブロックは残しています。ルールを渡して促し、それでも通ろうとしたら止める二段構えにしました。

$CLAUDE_PROJECT_DIR は、worktree の中でセッションを起動しても元のリポジトリのルートを指します(公式: Worktrees)。スクリプトを元のリポジトリに1か所置いておけば、どの worktree からでも同じものが動きます。

裏を返すと、hooks を作業ブランチで追加した場合、マージされるまで worktree 内のセッションではスクリプトが見つからず、ブロックは効きません。この記事のPRがまさにその状態です。

CLAUDE.md に書くだけでは足りない

同じルールを CLAUDE.md に書く手もあります。ただ、CLAUDE.md は「読んで守ってもらう」ものです。「ブランチを切って」と言い忘れたときに効いてほしいルールなので、機械的に止める hooks のほうが向いています。

CLAUDE.md には運用の説明を書き、止めるのは hooks に任せる。そういう分担です。


まとめ

いまの運用ルールはこうなりました。

  • 1タスク=1 worktree=1ブランチ=1 VS Codeウィンドウ。並列で進めるなら、まずディレクトリを分ける
  • worktree は .claude/worktrees/<名前>/ に置く。.gitignore に追加しておく
  • node_modules は worktree ごとに npm ci する
  • PRがマージされたら git worktree remove と git branch -d で消す
  • hooks で、セッション開始時にブランチ名を渡し、main 上の編集はブロックする

「ブランチを切って」と毎回言うのをやめて、言わなくても混ざらない状態を作る方向に切り替えました。セッションを2つ3つ開いていると、どれがどのブランチかは自分でも追えません。指示で縛るより、混ざらない構造を先に作るほうが楽でした。

Claude Codeを1年弱使ってきた全体の評価は実践レビューに書いています。AIに下書きを任せたときに何を間違えるかは、姉妹ブログのAIの下書きは、どこを間違えるのかにまとめました。