• #AutoHotKey
  • #Stream Deck
  • #Claude Code
  • #Windows Terminal
  • #AHK
開発claude-code-toolsメモ

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に並べると、どのボタンがどのペインかを覚える必要がない。左上のボタンが左上のペイン。

運用フロー

  1. pwsh -File launch-4pane.ps1で4ペインを起動
  2. 各ペインでclaude --resume
  3. Chromeでドキュメントを読みながら、許可プロンプトが出たらStream Deckの対応ボタンを押す
  4. 視線は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