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(Cowork)デスクトップを4分割で使い倒すmac環境構築 — Hammerspoon × hookでチャネル暴走を防ぐ

1
Last updated at Posted at 2026-04-27

結論

  • Option+1/2/4 でClaude Codeデスクトップ(Cowork)を1/2/4分割に瞬時配置できる
  • 仕組みは Hammerspoon + AppleScript + open -n80行程度のシェル
  • 並走しすぎで暴走するので、SessionStart hookで 4チャネルを超えたら起動拒否 する制御も同時に入れる

このセットアップで、Coworkを「左でリサーチ・右で実装・下でレビュー・もう1つでメモ」という4並列ワークフローが片手のショートカットで開ける。

完成形

┌─────────────────┬─────────────────┐
│  Claude #1      │  Claude #2      │
│  リサーチ       │  実装           │
├─────────────────┼─────────────────┤
│  Claude #3      │  Claude #4      │
│  レビュー       │  メモ           │
└─────────────────┴─────────────────┘

Option+4 一発でこの配置になる。Option+2 なら左右半分、Option+1 でフルスクリーン。

なぜ作ったか

CoworkはCmd+Nで新規ウィンドウを開けるが、配置は手動。並列でAIに作業させたい人間にとって、毎回ウィンドウサイズを揃えるのが地味に苦痛。

しかも放置すると「気づいたらバックグラウンドに10セッション残っていた」状態になる。プロセスもメモリも食う。

配置の自動化同時稼働数の上限 の両方が必要。

構成

役割 ツール 設定ファイル
ホットキー受け Hammerspoon ~/.hammerspoon/init.lua
ウィンドウ配置 bash + AppleScript ~/.claude/scripts/claude_split.sh
並走チャネル制御 Claude Code SessionStart hook ~/.claude/hooks/check-max-channels.sh
終了時の掃除 Claude Code SessionEnd hook ~/.claude/hooks/cleanup-channel.sh
タイル型WMとの共存 AeroSpace(任意) ~/.aerospace.toml

実装1: Hammerspoonでホットキー登録

~/.hammerspoon/init.lua

-- ===== Claude.app を複数起動 =====
hs.hotkey.bind({"cmd", "ctrl", "alt", "shift"}, "C", function()
  hs.execute("/usr/bin/open -n /Applications/Claude.app")
  hs.alert.show("Claude 起動", 0.5)
end)

-- ===== Claude 分割配置 (Option + 数字) =====
local function splitClaude(n)
  local home = os.getenv("HOME")
  hs.execute(string.format("/bin/bash %s/.claude/scripts/claude_split.sh %d", home, n))
  hs.alert.show("Claude " .. n .. "分割", 0.5)
end

hs.hotkey.bind({"alt"}, "1", function() splitClaude(1) end)
hs.hotkey.bind({"alt"}, "2", function() splitClaude(2) end)
hs.hotkey.bind({"alt"}, "4", function() splitClaude(4) end)

open -n は同じアプリの新規インスタンスを起こすコマンド。これでCoworkウィンドウを増やせる。

実装2: 配置スクリプト

~/.claude/scripts/claude_split.sh のコア部分(全体は80行):

#!/bin/bash
set -e
TARGET=${1:-2}

# 上限超過: TARGETより多いClaude本体プロセスはkill (新しい順)
PIDS=$(ps -A -o pid=,command= | awk '/Claude\.app\/Contents\/MacOS\/Claude/ && !/Helper/ {print $1}' | sort -n)
COUNT=$(echo "$PIDS" | grep -c .)
if [ "$COUNT" -gt "$TARGET" ]; then
  KILL_N=$((COUNT - TARGET))
  echo "$PIDS" | tail -n "$KILL_N" | xargs kill -TERM 2>/dev/null || true
  sleep 2
fi

# 画面ジオメトリを正確に取得 (メニューバー/ノッチ/Dockすべて考慮)
GEOM=$(osascript -l JavaScript -e '
ObjC.import("AppKit");
const f = $.NSScreen.mainScreen.frame;
const v = $.NSScreen.mainScreen.visibleFrame;
const menuBar = f.size.height - (v.origin.y + v.size.height);
const dockBottom = v.origin.y;
[Math.round(f.size.width), Math.round(f.size.height), Math.round(menuBar), Math.round(dockBottom)].join(",")
')

# 不足分は open -n で起動
NEEDED=$((TARGET - CURRENT_WINS))
if [ $NEEDED -gt 0 ]; then
  for ((i=0; i<NEEDED; i++)); do
    /usr/bin/open -n /Applications/Claude.app
    sleep 1
  done
  sleep 1.5
fi

# AppleScript で position と size を2x2グリッドに配置(4分割の場合)
# ...

ポイントは3つ:

  1. 画面ジオメトリの取得: NSScreenframevisibleFrame の差分でメニューバー・ノッチ・Dockの実寸を出す。固定値で組むとMacBookのノッチ世代とそれ以前で位置がズレる
  2. size を2回投げる: AppleScriptで一度position/sizeをセットしても、Coworkが内部リサイズで戻すケースがある。delay 0.05 で2回送ると安定
  3. 不足分は open -n で起動: 既存ウィンドウが2個しかない状態で Option+4 を押したら、自動で2個追加する

ハマり①: 再起動するたびにHammerspoonが死ぬ

ログアウト・再起動すると、HammerspoonもKarabiner-Elementsも常駐しない。pgrep -fl Hammerspoon で確認するとプロセスが居ない。

init.lua は無事なのに「ホットキーが効かなくなった」と感じるのはこれが原因。

対策: ログイン項目に追加する。

osascript -e 'tell application "System Events" to make new login item \
  at end with properties {path:"/Applications/Hammerspoon.app", hidden:true}'

hidden:true にすると起動時にウィンドウが出ない。メニューバーには常駐する。

確認:

osascript -e 'tell application "System Events" to get the name of every login item'
# => Raycast, Clipy, AeroSpace, Karabiner-Elements, Hammerspoon

ハマり②: バックグラウンドのCoworkチャネルが暴走する

Cowork本体ウィンドウは4個までだが、内部のセッション(並走チャネル)は何個でも生まれる。気づくと10個並走、メモリ食い放題、Dockのアイコンも何が何だか分からなくなる。

公式設定では並走数上限は出ていない(maxConcurrentSessions のような項目は2026-04時点で未実装)。

SessionStart hookで自前ガード を入れる。

~/.claude/hooks/check-max-channels.sh:

#!/bin/bash
set -euo pipefail

MAX_CHANNELS=4
CHANNEL_DIR="${TMPDIR:-/tmp}/claude-channels"
mkdir -p "$CHANNEL_DIR" 2>/dev/null

# 5分以上前のstaleチャネル削除(Cowork再起動時の同時発火対策)
find "$CHANNEL_DIR" -name "*.json" -mmin +5 -delete 2>/dev/null || true

EXISTING=$(find "$CHANNEL_DIR" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
TOTAL=$((EXISTING + 1))

if [ "$TOTAL" -gt "$MAX_CHANNELS" ]; then
  echo "❌ Coworkチャネル上限超過: ${TOTAL}/${MAX_CHANNELS}" >&2
  echo "   既に ${EXISTING} チャネル稼働中。先に余分なセッションを閉じてください。" >&2
  exit 2
fi
exit 0

~/.claude/settings.json の SessionStart に登録:

"SessionStart": [
  {
    "hooks": [
      {
        "type": "command",
        "command": "bash ~/.claude/hooks/check-max-channels.sh",
        "timeout": 5
      }
    ]
  }
]

exit 2 を返すとSessionStart hookは「セッション開始を拒否」する挙動になる。

ハマり③: 終了したセッションのゾンビファイルが残る

チャネル登録ファイルは $TMPDIR/claude-channels/ に書かれるが、Cowork側に「セッション終了時にこのファイルを消す」仕組みはない。放っておくとゾンビが溜まり、何もしてないのに「上限超えました」と拒否される。

SessionEnd hookで自動掃除

~/.claude/hooks/cleanup-channel.sh:

#!/bin/bash
set -euo pipefail
CHANNEL_DIR="${TMPDIR:-/tmp}/claude-channels"
[ -d "$CHANNEL_DIR" ] || exit 0

CHANNEL_CWD="${CLAUDE_PROJECT_DIR:-$PWD}"
CHANNEL_HASH=$(echo "$CHANNEL_CWD" | shasum 2>/dev/null | cut -c1-8)

# 同じcwdの最古のJSONを1件削除(このセッションが登録した分)
OLDEST=$(find "$CHANNEL_DIR" -name "${CHANNEL_HASH}-*.json" -type f \
  | xargs -I{} stat -f "%m %N" {} | sort -n | head -1 | awk '{print $2}')
[ -n "$OLDEST" ] && rm -f "$OLDEST"

# 10分以上前のstaleも掃除
find "$CHANNEL_DIR" -name "*.json" -mmin +10 -delete 2>/dev/null || true
exit 0

settings.jsonに SessionEnd フックとして登録すれば、終了のたびに自分の登録を消してくれる。

おまけ: AeroSpaceを使っている場合の衝突回避

AeroSpace(タイル型WM)は Option+数字 をワークスペース切替に使うのがデフォルト。Hammerspoonの Option+1/2/4 と被るので、AeroSpace側を退避する。

~/.aerospace.toml:

# alt-1/2/4 は Hammerspoon の Claude分割に譲る
# alt-1 = 'workspace 1'  ← コメントアウト
# alt-2 = 'workspace 2'
# alt-4 = 'workspace 4'

# 代わりに Cmd+Option に逃がす
alt-cmd-1 = 'exec-and-forget open -a "Google Chrome"'
alt-cmd-2 = 'exec-and-forget open -a "LINE"'
alt-cmd-4 = 'workspace 4'

# Claude.appはfloating指定(Hammerspoonが配置するため)
[[on-window-detected]]
if.app-id = 'com.anthropic.claudefordesktop'
run = ['layout floating']

タイル型WMがClaudeを勝手にタイル化するとHammerspoonの配置が上書きされるので、floating 指定が大事。

"設定が戻った" 問題の切り分けフロー

ホットキーが効かない・キー配列が標準に戻った気がする、と感じたとき、9割は 常駐プロセスが落ちているだけ

# 1. プロセス生存確認
pgrep -fl Hammerspoon
pgrep -fl karabiner

# 2. 死んでたら起動
open -a Hammerspoon
open -a Karabiner-Elements

# 3. ログイン項目に居るか確認
osascript -e 'tell application "System Events" to get the name of every login item'

# 4. 居なければ追加
osascript -e 'tell application "System Events" to make new login item \
  at end with properties {path:"/Applications/Hammerspoon.app", hidden:true}'

~/.hammerspoon/init.lua~/.config/karabiner/karabiner.json の中身は普通残っているので、設定ファイル自体を疑う前にプロセスを見る。

4並列ワークフローの具体例

「4分割できて何が嬉しいのか?」が一番気になるところだと思う。実際に自分が回している運用を晒す。

パターンA: リサーチ→実装→レビュー→記事化

ウィンドウ 役割 プロンプト例
左上 #1 リサーチ専任 「○○の公式ドキュメントを読んで、要点をMarkdownで返して」
右上 #2 実装専任 「#1の結論を元に△△を実装。Pythonで書いて」
左下 #3 レビュー専任 「#2のコードをセキュリティとパフォーマンス観点でレビュー」
右下 #4 記事化専任 「今回の試行錯誤をQiita記事のドラフトに」

人間は #1〜#4 を巡回するだけ。各セッションは独立で動くので、待ち時間が消える。1人で動かす時の体感生産性は3〜4倍。

パターンB: A/Bテスト型

同じタスクを別アプローチでやらせて比較する。

  • #1: 「Streamlitで実装して」
  • #2: 「Flask+HTMXで実装して」
  • #3: 「Next.jsで実装して」
  • #4: 結果比較・判断

技術選定の意思決定が一気に進む。「Aの方が良さそう」を実コードで判断できる。

パターンC: 長時間バッチ + 対話

  • #1〜#3: 長時間処理(データ整形・スクレイピング・大量ドキュメント要約)を投げて放置
  • #4: 自分が対話しながら作業

放置組が終わるまでの間、対話組で別作業。CPUとAPI課金を遊ばせない。

トラブルシューティング詳細

実際にハマって時間を溶かしたパターンを書いておく。

Q1: Hammerspoonのホットキーが反応しない

# まずプロセス確認
pgrep -fl Hammerspoon

# 居なければ起動
open -a Hammerspoon

# init.luaが読み込まれているか確認(Hammerspoonコンソールで)
hs.reload()

メニューバーのHammerspoonアイコン → Console を開いて、エラーが出ていないか見る。init.lua のシンタックスエラーは pop-up で出ないので、コンソール確認が必須。

Q2: AppleScriptで「アシスティブデバイスへのアクセス」エラー

osascript: script error: System Events got an error: ...

→ システム設定 → プライバシーとセキュリティ → アクセシビリティ で Hammerspoonosascript にチェックが入っているか確認。Macアップデートで権限がリセットされることがある。

Q3: SessionStart hookで exit 2 してもセッションが起動する

Cowork のバージョンによっては exit 2 の挙動が変わる可能性がある。確実に止めるなら exit 1 も試す。

# settings.json のhookでBlocking挙動を確認
cat ~/.claude/settings.json | python3 -c "import json,sys; print(json.load(sys.stdin)['hooks']['SessionStart'])"

Q4: チャネルファイルの場所が違う

$TMPDIR はサンドボックスの有無で変わる。実体はだいたいここ:

# ユーザー固有の一時ディレクトリ
echo "/var/folders/$(stat -f %i / | cut -c1-2)/$(getconf DARWIN_USER_CACHE_DIR | sed 's|.*/\([^/]*\)/C/.*|\1|')/T"

# または素直に
getconf DARWIN_USER_TEMP_DIR

getconf DARWIN_USER_TEMP_DIR で確実に取れる。スクリプトでハードコードするときはこれを使う。

応用編: SessionEnd hookで他にできること

SessionEnd フックはセッション終了時に1回呼ばれる。チャネル掃除以外にも色々使える。

1. セッション終了時にgit autosave

#!/bin/bash
# ~/.claude/hooks/auto-commit.sh
cd "$CLAUDE_PROJECT_DIR" 2>/dev/null || exit 0
if git diff --quiet 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
  exit 0  # 変更なし
fi
git add -A
git commit -m "WIP: claude session end $(date +%Y-%m-%d_%H:%M)" --allow-empty 2>/dev/null

「セッション中に編集したけどコミット忘れた」を物理的に防げる。

2. セッション要約をMarkdownに残す

#!/bin/bash
# ~/.claude/hooks/session-log.sh
LOG_DIR="$HOME/.claude/session-logs"
mkdir -p "$LOG_DIR"
{
  echo "## $(date '+%Y-%m-%d %H:%M') セッション終了"
  echo "- cwd: $CLAUDE_PROJECT_DIR"
  echo "- 変更ファイル:"
  cd "$CLAUDE_PROJECT_DIR" 2>/dev/null && git diff --name-only HEAD 2>/dev/null | sed 's/^/  - /'
  echo ""
} >> "$LOG_DIR/$(date +%Y-%m).md"

月単位で「いつ何を触ったか」が残る。月次振り返りに使える。

3. macOS通知でセッション終了を知らせる

#!/bin/bash
osascript -e "display notification \"セッション終了 ($(basename "$CLAUDE_PROJECT_DIR"))\" with title \"Cowork\""

長時間バッチをCowork で回しているとき、終わりを見逃さない。

ログイン項目を一括追加するスクリプト

毎回 osascript を打つのが面倒なので、まとめて追加するワンライナー。

#!/bin/bash
# ~/.claude/scripts/setup-login-items.sh
APPS=(
  "/Applications/Karabiner-Elements.app"
  "/Applications/Hammerspoon.app"
  "/Applications/AeroSpace.app"
  "/Applications/Raycast.app"
  "/Applications/Clipy.app"
)
for app in "${APPS[@]}"; do
  name=$(basename "$app" .app)
  exists=$(osascript -e "tell application \"System Events\" to get the name of every login item" \
    | tr ',' '\n' | sed 's/^ *//' | grep -Fx "$name" | head -1)
  if [ -z "$exists" ]; then
    osascript -e "tell application \"System Events\" to make new login item at end \
      with properties {path:\"$app\", hidden:true}" >/dev/null
    echo "✅ 追加: $name"
  else
    echo "⏭ 既存: $name"
  fi
done

新しいmacにセットアップする時、これ1本流せば常駐ツールが揃う。

ディスプレイ別の挙動と注意点

claude_split.shNSScreen.mainScreen を見るので、メインディスプレイにだけ配置される。マルチモニター環境で「サブディスプレイに4分割したい」場合は、mainScreenscreens()[1] に変える。

// JavaScript for Automation
const screens = $.NSScreen.screens;
const target = screens.objectAtIndex(1);  // 2番目のディスプレイ
const v = target.visibleFrame;

13インチMBP(2560×1664)で4分割すると1ペイン1280×768程度になる。Coworkのサイドバーがあると本文領域は狭くなるので、各ペインでサイドバーを畳む(Cmd+B 等)と作業しやすい。

外部4Kモニター(3840×2160)なら4分割で1920×1040、これが一番快適。

4並列で動かす時のコスト感

Coworkは1セッションあたり一定のContext Windowを持つので、4セッション同時に動かすと単純計算でAPI呼び出し量も4倍。Maxプランなら気にしなくて良い範囲だが、従量課金で動かすと月のトークン消費は急増する。

ざっくり目安:

  • 1セッション軽め利用: 月200万トークン(Sonnet基準で約30ドル)
  • 4セッション並列で本気利用: 月800万トークン(同 約120ドル)

Maxプラン(月100ドル定額)の方が圧倒的に得。並列前提で使うならMax固定で問題ない。

まとめ

今日からやること

  1. 今日: Hammerspoonをインストールして init.lua に Option+1/2/4 を仕込む(10分)
  2. 今週: claude_split.sh~/.claude/scripts/ に配置 → 動作確認 → ログイン項目に追加(30分)
  3. 今月: SessionStart/SessionEnd hookでチャネル管理を仕組み化(応用編まで含めて1時間)

この記事のキモ

  • Coworkを4分割で使うには Hammerspoon + 80行のbashで十分
  • 再起動で死ぬツールはログイン項目に hidden:true で追加
  • 並走チャネルが暴走するなら SessionStart hookで exit 2
  • ゾンビファイルは SessionEnd hookで自動掃除
  • macOSの一時ディレクトリは getconf DARWIN_USER_TEMP_DIR で確実に取れる

体感、Coworkを4並列で動かせるようになると思考の解像度が変わる。並列でAIに別観点の作業をさせて、自分は司令塔に徹するスタイルが取れる。

「AIを使う人」から「AIに作業を分担させる人」への移行は、この手のオペレーション設計から始まる気がしている。


参考:

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?