こんにちは、エンジニアのオオバです。
僕は作業枠(Dev1・Dev2…という作業用のフォルダ)ごとに Claude Code や Codex を並べて動かしています。
どの枠でAIが動いているかは、自作の Dev Slots というツールでメニューバーに出しています。
ところが、このツールは「作業中」しか見ていませんでした。
AIのターンが終わった瞬間に、枠の表示が空きに戻ってしまう。
「終わったなら空いたんでしょ?」って思いますよね。
結論、空いていません。
AIが「どちらで進めますか?」と返事を待っていたり、PRがまだマージされていなかったりするからです。
その枠に別のAIを割り当てると、二重アサインの事故が起きます。
ということで本記事では、Dev Slots に「回答待ち」と「確認待ち」の2つの状態を足した話をお届けします。
AIの最後の返答に質問や依頼があれば「回答待ち」(青)、未コミットか未マージのコミットがあれば「確認待ち」(紫)。マージ時刻か「完了にする」を押した時刻が最後の返答より後なら空きに戻す
先に結論をまとめます。
①回答待ち(青):最後に触ったAIの返答に「?」の行や返事が要る依頼がある
②確認待ち(紫):git statusに変更があるか、origin/developへ未マージのコミットが残っている
③完了時刻(マージ時刻と手動の完了の新しい方)がAIの最後の返答より後なら空きに戻す
ざっくり言うと、前半が2つの状態の判定方法、後半が実際に使ってみて出た誤判定とその直し方です。
では本編に入っていきます。
→11万文字で徹底解説した「DOTweenの教科書」Unityアニメーションの超効率化ツールはこちら
やりたかったこと:空いていない枠を空きに見せない
表示の優先順位は次のようにしました。
- 衝突(赤)
- 作業中(オレンジ)
- 回答待ち(青)
- 確認待ち(紫)
- 空き(白)
回答待ちは、記号を C(Claude Code)/ X(Codex)のまま色だけ青にしています。
確認待ちは紫で、担当のAIが分からない枠は「•」で出します。
マージ済みで変更も無い枠は、空きに戻ります。

これまでの表示はこんな感じです。

作業中のオレンジしか色が無く、AIが手を止めた枠は全部ただの点になっていました。
回答待ちの判定:最後の返答が質問で終わっているか
最初の実装は、最後に触ったツールの返答が質問で終わっているかを見る形です。
Claude Code
トランスクリプトの末尾の返答が「?」で終わっていれば回答待ちにしました。
末尾の ** や「」」は無視しています。
AskUserQuestion や ExitPlanMode の返事を待っている状態も、回答待ちとして数えます。
Codex
Codex は、rollout の task_complete イベントに last_agent_message(最後の返答)が入っています。
その末尾を見るだけで済みました。
後から別のツールが触ったら待っていない
同じ枠を後から別のツールが触った場合、その質問はもう待っていないとみなします。
実ログで確かめると、質問で終わっていたのは Codex が直近145ターン中0件、Claude が直近80セッション中2件。
誤判定はほぼ出ない数字です。
ハマった点:質問ボックスで終えたターンが「作業中」に見えていた
Claude デスクトップアプリは、ターンの最後に質問ボックス(mcp__ccd_turn__wrap_up の ask)を出します。
このとき、トランスクリプトの最後のレコードは assistant の end_turn になりません。
ツール結果を返す user レコードが最後に来ます。
従来の判定は「最後が user ならプロンプト送信直後なので作業中」でした。
そのため、ユーザーの返事待ちなのに、staleMinutes の30分のあいだ作業中と表示されていたわけです。
ツール結果のレコードのときは、tool_use_id で直前の assistant レコードの tool_use まで遡るように直しました。
そこでツール名と input の ask を見て、質問ボックスなら回答待ちにしています。
確認待ちの判定:gitだけで見る
確認待ちは、git のコマンドだけで判定しています。
未コミットと未マージ
未コミットの変更は、未追跡ファイルを含めて見ます。
git status --porcelain
マージ済みかどうかは、HEAD が origin/develop の祖先になっているかで確かめます。
git merge-base --is-ancestor HEAD origin/develop
squashマージ対策はmerge-tree
squash マージだと、HEAD のコミットは origin/develop の祖先になりません。
そこで git merge-tree を使いました。
git merge-tree --write-tree origin/develop HEAD
結果のツリーが origin/develop のツリーと同じなら、「マージしても何も変わらない」のでマージ済みとみなします。
--write-tree は git 2.38 以降の機能で、macOS 標準の Apple Git 2.39.5 で使えます。
負担は5秒ごとのスキャンでも問題なし
1枠あたりの時間は、git status が0.09秒、merge-tree が0.02秒でした。
5秒ごとにスキャンしても負担になりません。
origin/developを裏でfetchする
GitHub で PR をマージしても、手元の origin/develop は fetch するまで古いままです。
そこで base の1ブランチだけを、定期的に裏で git fetch しています。
スキャンを待たせないよう、subprocess.Popen(start_new_session=True) で投げっぱなしにしました。
fetch の結果は次のスキャンで使います。
ちなみに、gh で PR の状態を見る案は採りませんでした。
なぜなら SSH で見に行く別の Mac にまで、gh の認証を求めたくなかったから。
確認:テスト38件と、テスト環境での見た目の確認
テストは38件書きました。
- 質問の判定
- Codex の長い返答が、読み込みの塊の境目で切れるケース
- squash マージ済みの判定
見た目は、一時フォルダを HOME にしたテスト環境でアプリをもう1つ起動して確かめています。
ところがメニューバーが混んでいて、ステータス項目がノッチの裏に隠れてしまいました(笑)。
そこで CGWindowListCopyWindowInfo でステータス項目のウィンドウIDを取り、screencapture -l で直接撮っています。
冒頭の青と紫の画像は、この方法で撮ったものです。
誤判定1:質問を並べたあとに補足を書いて終える返答が「空き」になった
使い始めてすぐ、取りこぼしが見つかりました。
MacBook Air の Dev3 で、Claude が「確認したいこと」として質問を7つ並べていました。
そのあとにマップの叩き台を書いて、「→ ADVへ」で終えています。
判定は「最後の1文が?で終わるか」だったので、空き表示のまま。
僕からも「ボタンを押させる確認じゃなくて、ただ自分に確認して回答が欲しいときも待ちにしてほしい」と頼みました。
返答全体から質問と依頼を探す
判定を、返答全体から次のどちらかを探す形に変えています。
- 「?」で終わる行
- 依頼の言い回し(教えてください・決めてください・選んでください・確認して〜ください・確認をお願い・指示をください)
実ログ約470返答で確かめました。
途中にだけ質問がある返答は4件。
3件は本当に返事待ちで、残りの1件は引用(>)したブログ下書きの中の「?」でした。
引用・コード・URL の中は見ないようにしています。
「違ったら教えてください」は待っていない
依頼の言い回しは56件ありました。
多くは「内容を確認して、修正があれば教えてください」のような確認の依頼で、これは待ちとして数えます。
一方で「違ったら教えてください」「何か落ちていたら教えてください」は、返事を待っていません。
正規表現の否定の後読みで外しました。
(?<![らば])教えてください
1つの返答が別レコードに分かれている
Claude Code のトランスクリプトは、1つの返答が thinking と text の別レコードに分かれます。
同じ message.id を遡って全文をつなげてから判定するようにしました。
表示の調整:MacごとにP / Aの座布団バッジ
僕は MacBook Pro と MacBook Air の2台で枠を動かしています。
どちらの Mac で見ても MBP → Air の順に固定し、先頭に座布団つきの白抜き文字 P / A を置きました。
区切り線はやめています。

座布団はメニューバーの文字色で塗る
座布団は、角丸の板をメニューバーの文字色(labelColor)で塗っています。
文字は destinationOut のブレンドで抜き、その画像を NSTextAttachment で差し込みました。
ただ、そのまま抜くとメニューバーの背景まで抜けてしまいます。
そこで CGContext の beginTransparencyLayer の中で描いています。
描画のたびに塗るので、壁紙が変わって文字色が白と黒で入れ替わっても追従します。
ちなみに一度10ptに小さくしたら小さすぎたので、13ptに戻しました。
誤判定2:「〜してください」は依頼とは限らない
依頼の言い回しを足したら、今度は完了済みの MacBook Air Dev1 が回答待ち(青)になりました。
原因は完了報告の「気をつけてほしいこと」にあった、次の一文です。
「家や木など複数マスの物は…物全体を囲んで選んでください」
僕への依頼に見えて、中身はツールの使い方の説明だったわけです。
形まで見て数える
次のように直しました。
- 「選んでください」は「01か02を選んでください」のように「〜か」を伴う形だけ数える
- 「〜で確認してください」「ご確認ください」は数えない。「Slackでの見え方は平日朝の定期実行で確認してください」のような作業手順の案内にも使うため
- 返事が要る「内容を確認して、修正があれば教えてください」「確認をお願いします」「決めてください」は残す
実ログ約470返答で見直すと、依頼だけで回答待ちになるのは約50件。
どれも本当に返事待ちでした。
正規表現で「依頼」を拾うときは、動詞だけ見ていると使い方の説明まで拾ってしまいます。
「AかBを〜」「条件つきの〜たら」のような 文の形 まで見る必要があるということ。
誤判定3:マージしたのに空きに戻らない
次に来たのが「マージしたのに点(空き)に戻らない」という問題です。
2枠を調べると、原因はそれぞれ別でした。
MBP Dev2:fetchの間隔が長かった
PR は10:37:53にマージ済みでした。
ところが裏の git fetch が10:36に走ったばかりで、次の fetch は10:41。
それまで手元の origin/develop が古く、紫(未マージ1コミット)のままでした。
fetch の既定の間隔を5分から1分に縮めています。
Air Dev2:git管理外のロックファイルが残っていた
こちらは PR がマージ済みで、未マージのコミットもありません。
それでも、社内ツールが作るロックファイル master-admin-sync.lock(git 管理外)が残っていて、「未コミット1件」で紫のままでした。
ファイルの状態だけを見ていると、こういう副産物で誤判定します。
マージした時刻で「完了」を判定する
そこで「マージで完了した枠」を時刻で判定するようにしました。
HEAD を base に取り込んだ最初のマージコミットの時刻を、次のコマンドで取ります。
git log --ancestry-path --merges --reverse --format=%ct HEAD..origin/develop
その時刻が「最後に触ったAIの最後の返答」より後なら、マージで作業が完了したとみなして空きにします。
Air Dev2 は、Codex の最後の返答が10:19、マージが10:20で条件を満たしました。
マージ後にAIへ続きを頼めば、最後の返答がマージより後になるので、通常どおり判定されます。
「マージしたけどまだタスクが残っている」ケースもこれで拾えます。
しかも「マージしてよいですか?」で回答待ちになっていた枠も、マージ自体が返事なので空きに戻ります。
最後の手段:メニューの「完了にする」で手で戻す
それでも自動判定で戻らない枠は残ります。
マージが不要だった作業や、判定しきれない返答などです。
そこで、回答待ち・確認待ちの枠の下に「✓ 完了にする(空きに戻す)」を足しました。
手動の完了もマージと同じ式に乗せる
押した時刻を記録し、マージ時刻と同じ扱いにしています。
完了時刻はマージ時刻と押した時刻の新しい方で、それがAIの最後の返答より後なら空き。
自動の判定と手動の判定を同じ式に乗せたので、コードはほぼ増えていません。
AIが続きを始めれば最後の返答が完了より後になるので、自然に通常の表示へ戻ります。
取り消しの操作は作りませんでした。
記録は枠があるMacに置く
完了の記録は、枠がある Mac に置いています。
MBP から Air の枠を完了にすると、判定スクリプト自体を SSH の標準入力で送り、--done を付けて実行して Air 側に書きます。
どちらの Mac のメニューにも反映されます。
学び:AIの状態は「止まったか」だけでは足りない
振り返ると、最初の「作業中かどうか」だけの表示は、信号機に青と赤しか無いようなものでした。
止まっている車が、信号待ちなのか駐車しているのかが分からない。
AIのターンが終わっても、僕の返事待ちやレビュー待ちで枠はふさがっています。
もう1つの学びは、自動判定には必ず取りこぼしが出るということです。
質問の判定もマージの判定も、実ログで見直すたびに例外が出てきました。
最後は人が「完了」と言える出口を作っておくと、判定の精度を上げ続けなくて済みます。
まとめ
①AIのターンが終わっても、回答待ちと確認待ちの枠は空いていない
②回答待ちは返答全体の「?」の行と依頼の言い回しで判定する。依頼は「〜か選んで」「〜たら教えて」など文の形まで見る
③確認待ちはgit status・merge-base・merge-treeで判定し、origin/developは裏で1分ごとにfetchする
④マージ時刻か手動の完了時刻がAIの最後の返答より後なら空きに戻す
AIを何本も並べて動かしていると、どの枠が本当に空いているのか分からなくなりがちですよね。
色が2つ増えただけで、二重アサインの心配がだいぶ減りました。
同じように複数のAIを並べて使っている人の参考になれば嬉しいです。

この記事が気に入ったらフォローしよう
- macOS 15 / Apple Git 2.39.5 / Python 3.9 / Swift (AppKit)
