AutoHotKey + Stream DeckでClaude Codeマルチペイン制御
Claude Codeを4ペインで並行実行していると、許可プロンプトが4つのペインから次々と飛んでくる。Chromeでドキュメントを読みながら、マウスを動かしてWindows Terminalに切り替え、正しいペインをクリックし、Enterを押し、またChromeに戻る。この往復を1日50回以上繰り返していた。Stream Deckのボタンを押すだけで済むようにした話。
4ペイン起動の土台: launch-4pane.ps1
まず、Windows Terminalを4分割で立ち上げるスクリプトを作った。各ペインにPANE_POS環境変数(top-left等)をセットし、タブタイトルをCC:top-leftのように設定する。
# launch-4pane.ps1(核心部分のみ)
function Encode-Command {
param([string]$Script)
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Script))
}
$encTL = Encode-Command "`$env:PANE_POS='top-left'; Set-Location '$TopLeft'"
wt -w 0 new-tab --title "CC:top-left" pwsh -NoExit -EncodedCommand $encTL `; `
split-pane --horizontal --title "CC:bottom-left" pwsh -NoExit -EncodedCommand $encBL `; `
move-focus up `; `
split-pane --vertical --title "CC:top-right" pwsh -NoExit -EncodedCommand $encTR `; `
move-focus down `; `
split-pane --vertical --title "CC:bottom-right" pwsh -NoExit -EncodedCommand $encBR
ポイントが2つある。
-w 0で既存WTにタブ追加:wt new-tabだけだと新しいウィンドウが開く。-w 0を付けると既存のWindows Terminalにタブとして追加される-EncodedCommandでBase64エンコード:wtコマンドのセミコロン区切りとPowerShellのセミコロンが衝突する。-Command方式ではエスケープが入れ子になって崩壊するため、Base64に逃がした
最初の試み: 自動フォーカス方式
最初に思いついたのは「許可プロンプトが出たら自動でそのペインにフォーカスを移す」という仕組みだった。Claude CodeのNotificationフックからAHKスクリプトを呼び出し、WinActivateでWindows Terminalを最前面に持ってくる方式。
WT_SESSIONでペイン識別を試みる
各ペインのWT_SESSION環境変数をキーにして、どのペインから通知が来たかを特定しようとした。しかし、WTウィンドウが2つ開いていると、両方がahk_exe WindowsTerminal.exeにマッチしてフォーカスの奪い合いが起きた。
SetForegroundWindowの壁
Windowsには「バックグラウンドプロセスが勝手にフォアグラウンドを奪えない」というフォアグラウンドロック制約がある。Altキーのシミュレートで回避するハックを試したが、動いたり動かなかったりで信頼性が低かった。
致命的だったのは「別アプリ誤爆」
最も深刻な問題は、SetForegroundWindowが失敗したとき、SendKeysが意図しないウィンドウに飛ぶリスクがあること。Chromeの入力フォームにEnterが飛んでフォームが送信される、といった事故が想像できた。Codexにレビューを投げたところ、まさにこの点を「致命的」と指摘された。
ここで自動フォーカス方式を諦めた。
方針転換: Stream Deck + AHK
考え直して、「自動」を捨てた。ユーザーが明示的にボタンを押して承認する。その代わり、マウスを動かさずChromeを見たまま操作できるようにする。
Stream Deck XLにはボタンが32個ある。4つ使うだけでいい。
claude-approve-pane.ahk
核心のAHKスクリプト。Stream Deckから呼ばれ、指定ペインにEnterを送信して元のウィンドウに戻す。
; 元のウィンドウを記憶
originalHwnd := WinGetID("A")
; CC: タイトルの WT を特定(部分一致)
SetTitleMatchMode 2
if !WinExist("CC: ahk_exe WindowsTerminal.exe") {
if !WinExist("ahk_exe WindowsTerminal.exe") {
ExitApp
}
}
targetHwnd := WinGetID()
WinActivate "ahk_id " targetHwnd
; 左上にリセットしてから目的のペインへ移動
Send "!{Left}" ; Alt+Left x2, Alt+Up x2 で左上到達
; ... 目的ペインに Alt+矢印で移動 ...
Send "{Enter}" ; 承認
; 元のウィンドウに戻す
Sleep 200
WinActivate "ahk_id " originalHwnd
設計で気を配った点:
CC:タイトルでWT識別:launch-4pane.ps1で設定したタイトルに部分一致させる。複数WTが開いていても4ペインのWTだけをターゲットする- ウィンドウハンドル固定:
WinGetID()でハンドルを取得し、以降はahk_idで操作する。途中でフォーカスが移っても別ウィンドウにキーが飛ばない - 名前付きMutexで排他制御: 4ボタン同時押しでもキーストロークが干渉しない
- 元のウィンドウに自動復帰: Chromeを見たまま操作が完結する
Stream Deckの設定
4つのbatファイルを作り、Stream Deckの「システム > 開く」アクションに登録した。
@echo off
"C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe" ^
"C:\Users\numbe\Git_repo\AutoHotkey\claude-approve-pane.ahk" top-left
ボタンをWTのペイン配置と同じ2x2に並べると、どのボタンがどのペインかを覚える必要がない。左上のボタンが左上のペイン。
運用フロー
pwsh -File launch-4pane.ps1で4ペインを起動- 各ペインで
claude --resume - Chromeでドキュメントを読みながら、許可プロンプトが出たらStream Deckの対応ボタンを押す
- 視線はChromeのまま、指だけが動く
自動フォーカス → Stream Deckへの転換で学んだこと
自動化の理想は「何もしなくても全部やってくれる」だが、Windowsのフォアグラウンドロックという制約がそれを阻んだ。SetForegroundWindowが失敗するケースを完全に潰すのは現実的でなく、失敗時のリスク(別ウィンドウへのキー誤送信)が大きすぎた。
「自動」を「半自動」に一段落とした瞬間、問題がすべて消えた。Stream Deckは物理ボタンなので、押し間違えても被害はEnterが1回空振りするだけ。フォアグラウンドの制御権もAHKがWinActivateで確実に取れる(ユーザー操作として扱われるため)。
全自動に固執して複雑な回避策を積み上げるより、「人間が1アクション担う」と割り切った方がシステム全体の信頼性が上がるケースがある。今回はまさにそれだった。
ファイル構成
C:\Users\numbe\Git_repo\AutoHotkey\
├── claude-pane-control.md # 運用ドキュメント
├── claude-approve-pane.ahk # メインスクリプト(Stream Deck用)
├── claude-auto-focus.ahk # 旧・自動フォーカス(未使用、参考用)
├── launch-4pane.ps1 # 4ペイン一括起動
├── sd-approve-top-left.bat # Stream Deckラッパー x4
├── sd-approve-top-right.bat
├── sd-approve-bottom-left.bat
└── sd-approve-bottom-right.bat