CodexのComputer Useを触っていると、「画面を見て、クリックして、入力する」だけでは足りない場面が出てきます。
今回ひっかかったのは、macOS版CodexのAppShotでした。AppShotは、前面アプリのウィンドウを画像と取得可能なテキストとしてCodexスレッドへ渡す機能です。公式ドキュメントでも、前面ウィンドウをCodexに渡す文脈共有手段として説明されています。
普通に使うなら、人間が両方のCommandキー、または設定したAppShotホットキーを押せばいい。ところが、AIエージェントにGUI操作を自動で任せるなら話が変わります。
アプリを操作する。画面が変わる。そこでまた画面状態を観測し、次の判断をする。このループを作るには、AppShotも必要なタイミングでCodex側から自律的に発火できないといけません。

クリックだけできても、画面が変わったあとに見直せないと、ただの一発芸なんですよね。
なぜAppShotを自律発火したかったのか

今回の出発点は、AppShotを単発の手動スクリーンショットとしてではなく、GUI操作中の再観測手段として使いたい、というところにありました。
操作後の画面をもう一度見る必要があった
Computer Useは、Macアプリを操作するためのかなり強い手段です。クリック、キー入力、スクロール、アクセシビリティツリーの確認などができるので、アプリのUI操作をかなり自然に扱えます。
ただし、GUI自動化では「操作」と同じくらい「再観測」が重要です。
たとえばCleanMyMacのスマートケアを自動化するとします。最初にアプリを起動し、トップ画面を見て、スキャンボタンを押す。すると画面は当然変わります。次に必要なのは、変化後の画面をもう一度観測して、次に押すべきボタンや確認すべき状態を判断することです。
このときAppShotが効きます。AppShotは単なるスクリーンショットではなく、取得可能なテキスト情報もCodex側へ渡せます。つまり、Computer Useの「操作」とAppShotの「観測」を組み合わせると、AIエージェントが次の画面状態を踏まえて判断し直せるようになります。
ここが面白いところです。AIエージェントのGUI操作は、クリック精度だけの問題ではありません。見て、動かして、また見る。このサイクルをどれだけ安定して回せるかが実用性を決めます。
Computer Useは操作の手段、AppShotは再観測の手段です。両方をつなぐと、AIエージェントは「押したあとに見直して判断する」流れへ近づきます。
Computer UseだけではCmd+Cmdを押せなかった

最初は当然、Computer UseでそのままAppShotホットキーを押せば済むと思っていました。ところが、ここで入力レイヤーの制限にぶつかります。
修飾キーだけの入力が制限に当たった
最初に試したのは素直な方法でした。Computer UseでAppShotのホットキー、つまり左右のCommandキーを押せばいいのではないか、という発想です。
しかし、ここで制限に当たりました。Computer Useのキー入力は、通常のキーや修飾キー付きショートカットには使えますが、左右Commandキーだけを同時に押すような「bare modifier」系の入力は弾かれました。
keyPressIncludedNoNonModifierKeysこれは雑に言えば、「修飾キーだけのキー入力は扱わない」という制限です。AppShotのデフォルト操作はまさにそこに当たります。
この制限自体は理解できます。Computer Useの通常キー入力としては、何かしらの非修飾キーを含むショートカットを扱うほうが自然です。問題は、AppShot側のUIが両Commandキーのような修飾キーのみのホットキーを採用していることでした。
つまり、Computer Useから見れば入力できない。AppShotから見ればそれが正規の起動方法。ここに小さな断絶がありました。

この断絶を見落とすと、「権限はあるのに、なぜか自律化できない」というかなり嫌な詰まり方をします。
失敗した経路は捨てる

回避策を作るときに重要なのは、試行錯誤の全部を残すことではありません。自律実行に使うなら、失敗した経路を再選択しない形に整理する必要があります。
再利用するなら失敗ルートを残さない
この手の問題で怖いのは、「それっぽく動きそうな経路」を残してしまうことです。
今回も、いくつかの経路を試しました。修飾キー状態の変化として投げる方法、bare modifierを監視するような検証経路などです。しかし、AppShotを実際に発火させる経路としては不採用にしました。
重要なのは、失敗した経路をコメントや予備ルートとして残さなかったことです。
GUI自動化では、たまたま動くように見える回避策があとで事故の種になります。特にエージェントが自律的に再利用するコマンドでは、「どの経路が正本なのか」が曖昧だと、将来のセッションでまた失敗経路へ戻ってしまう。
だから今回は、動かなかった経路をソースから消し、実働確認できた経路だけを残すことにしました。
失敗した検証経路を残すと、将来のCodexがそこへ戻る可能性があります。自律運用に使う helper では、成功経路だけを正本化する方が安全です。
動いた経路はCGEventのkeyDown/keyUpだった

最終的に採用したのは、対象アプリを前面化したうえで、macOSの低レベル入力イベントとして左右Commandキーを押す経路です。
対象アプリを前面化してから低レベル入力を投げる
最終的に動いたのは、低レベルのCGEventで左右CommandキーのkeyDown/keyUpを投げる方法でした。
流れはシンプルです。ただし、単にキーイベントを投げるのではなく、対象アプリの確認を前段に置きます。
bundle idで対象アプリをactivateし、少し待ってからfrontmost appが想定通りか確認します。
Swift helperからCGEventを使い、左Command down、右Command down、右Command up、左Command upの順でAppShotホットキーを再現します。
実際のヘルパーは、毎回その場でSwift断片を生成するのではなく、正本コマンドとして置きました。
/Users/suzukimakoto/.codex/bin/codex-appshot-trigger中身の役割は、wrapper側で対象アプリを前面化して確認し、Swift側で左右CommandのCGEventを投げることです。まずwrapper側の核はこうです。
APP_ID=""
WINDOW_TITLE=""
WAIT_MS="500"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELPER_SOURCE="$SCRIPT_DIR/codex-appshot-trigger.swift"
HELPER_BIN="$SCRIPT_DIR/.codex-appshot-trigger-helper"
if [[ "$REBUILD_HELPER" == "1" || ! -x "$HELPER_BIN" || "$HELPER_SOURCE" -nt "$HELPER_BIN" ]]; then
swiftc "$HELPER_SOURCE" -o "$HELPER_BIN"
chmod +x "$HELPER_BIN"
fi
osascript - "$APP_ID" <<'APPLESCRIPT'
on run argv
set bundleId to item 1 of argv
tell application id bundleId to activate
end run
APPLESCRIPT
sleep "$(awk "BEGIN { printf \"%.3f\", $WAIT_MS / 1000 }")"
FRONTMOST_ID="$(osascript <<'APPLESCRIPT'
tell application "System Events"
set frontProc to first application process whose frontmost is true
return bundle identifier of frontProc
end tell
APPLESCRIPT
)"
if [[ "$FRONTMOST_ID" != "$APP_ID" ]]; then
echo "Frontmost app mismatch: expected $APP_ID, got ${FRONTMOST_ID:-<none>}" >&2
exit 3
fi
"$HELPER_BIN"そして、AppShotホットキーを実際に発火しているSwift側はかなり小さいです。ここで重要なのは、失敗した flagsChanged 経路ではなく、通常の keyDown/keyUp イベントだけを使っている点です。
import CoreGraphics
import Foundation
let source = CGEventSource(stateID: .hidSystemState)
func postKey(key: CGKeyCode, down: Bool) throws {
guard let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: down) else {
throw NSError(domain: "codex-appshot-trigger", code: 1)
}
event.post(tap: .cghidEventTap)
}
do {
// 55 = left command, 54 = right command on macOS virtual key codes.
try postKey(key: 55, down: true)
usleep(80_000)
try postKey(key: 54, down: true)
usleep(250_000)
try postKey(key: 54, down: false)
usleep(80_000)
try postKey(key: 55, down: false)
} catch {
fputs("Failed to post AppShot hotkey CGEvents: \(error)\n", stderr)
exit(5)
}
じぴ子実装としては小さいですが、対象アプリ確認とkeyDown/keyUp経路を正本化したことで、次のセッションでも同じ判断を再利用できます。
使い方はこうです。
/Users/suzukimakoto/.codex/bin/codex-appshot-trigger --app com.macpaw.CleanMyMac5 --window-title CleanMyMac
/Users/suzukimakoto/.codex/bin/codex-appshot-trigger --app md.obsidian --window-title Obsidianこのヘルパーは、AppleScriptで対象アプリを前面化し、System Eventsでfrontmost bundle idを確認し、そのうえでSwift側のCGEvent発火へ進みます。ポイントは、単にキーイベントを投げるだけではなく、対象アプリが本当に前面にいるかを確認してからAppShotホットキーを発火することです。
ここを省くと、「別アプリのAppShotを撮ってしまう」「意図しない画面をCodexへ渡してしまう」という事故が起きます。AppShotは観測手段なので、観測対象の取り違えは操作ミスよりも危険です。
じぴ子AppShotは「見える化」ではなく「Codexへ渡す観測」なので、対象アプリ確認はかなり大事です。
これは制限の破壊ではなく、レイヤーを分けた回避

ここでやったことは、Computer Useの制限を無視することではなく、役割を分けることでした。通常操作と特殊なホットキー発火を同じ道具に押し込まない、という整理です。
Computer Useに不得意な入力を押し付けない
今回やったことは、Computer Useの制限を無理やり壊したわけではありません。
Computer Useの通常キー入力では扱えない入力があった。そこで、Computer Useにその入力をやらせるのではなく、macOSの低レベル入力イベントを投げる専用ヘルパーに責務を分けました。
つまり、こういう分担です。
| 役割 | 担当すること |
|---|---|
| Computer Use | 画面を見ながらクリック、入力、スクロールする主操作 |
| AppShot | 画面状態を画像とテキストでCodexに渡す観測 |
| codex-appshot-trigger | AppShotホットキーを再現可能に発火する補助コマンド |
| AGENTS.md | どの条件でどの経路を使うかを定義する運用契約 |
この分担にしたことで、Computer Useに不得意なことを押し付けずに済みます。そして、毎回その場の思いつきでSwiftを書かず、検証済みのコマンドを再利用できます。
地味ですが、ここがかなり大事です。AIエージェントにとって「前にうまくいった手順を、次のセッションでも自然に選ぶ」ことは、単発の成功より価値があります。
今回の肝は、低レベル入力そのものではなく、役割を分けて再利用可能な正本コマンドにしたことです。
AGENTS.mdに昇格したこと

動いた手順は、その場のノリで終わらせると次のセッションで失われます。そこで今回は、使う条件と停止条件をAGENTS.mdへ明文化しました。
単発の成功を次回も使える運用契約にした
今回の知見は、その場限りのメモではなく、AGENTS.mdに昇格しました。
書いた内容の要点は次の通りです。
CMPはComputer Useの略称として扱う- macOS GUIアプリ操作では、主操作手段をComputer Useにする
- AppShotは、前面ウィンドウを画像と取得可能テキストとしてCodex threadへ渡す観測手段として扱う
- 対象アプリと作業目的が明示されているGUI workflowでは、必要に応じてCodexが自律的にAppShotを再取得してよい
- AppShot発火には
/Users/suzukimakoto/.codex/bin/codex-appshot-triggerを使う - 失敗確認済みの
flagsChanged経路やbare-modifier-monitor検証経路へ戻さない - frontmost appが想定bundle idと違う場合、対象アプリが変わる場合、認証、権限昇格、外部送信、削除、課金、不可逆変更に入る場合は停止して確認する
ここで大事なのは、「自律的にAppShotを撮ってよい」と「何でも勝手に撮ってよい」は違う、という点です。
対象アプリと目的が明示されていて、その作業範囲内の再観測として必要な場合は、毎回ユーザーに確認しない。これは実用性のために必要です。一方で、対象が変わる、権限や課金や削除に入る、不可逆操作に入る場合は止まる。これは境界管理です。
じぴ子「毎回確認しない」と「何でも勝手にやる」は別物です。ここを分けないと、実用性か安全性のどちらかが壊れます。
Computer UseとAppShotを組み合わせると何が変わるか

Computer UseとAppShotを分けて考えると、GUI自動化の見え方が少し変わります。操作だけでなく、操作後に状態を取り直す流れを作れるからです。
クリック代行から観測ループへ変わる
Computer Use単体でもGUI操作はできます。しかし、AppShotを必要なタイミングで再取得できるようになると、操作の意味が変わります。
単発のクリック代行から、観測と判断を含むループへ近づきます。
たとえば、次のような流れが現実的になります。
- 対象アプリを起動する
- AppShotで現在状態を取得する
- Computer Useでボタンを押す
- 画面遷移後にAppShotを再取得する
- 新しい画面状態を読んで次の操作を判断する
- 目的達成まで繰り返す
これは「画面を見ているふり」ではありません。状態が変わったら、観測も更新する。エージェントがGUIアプリを扱うなら、この再観測の仕組みがないとすぐに脆くなります。
今回のAppShot helperは、そのループの中で「観測を取り直す」ための小さな部品です。でも、その小さな部品がないと、全体の自律性が止まります。
GUI自動化を安定させるには、操作ログよりも「画面変化後に再観測できること」が効いてきます。
もちろん万能ではない

ただし、この方法は何でも自律化できる魔法ではありません。AppShotは強い観測手段なので、使う範囲と止まる条件をセットで考える必要があります。
自律化するほど停止条件が重要になる
この方法は万能ではありません。
macOSの権限設定、Codex appのバージョン、AppShotのホットキー仕様、対象アプリの前面化の挙動によって変わる可能性があります。すべての環境で同じように動くとは書けません。
また、AppShotは強い観測手段です。画面に出ている内容をCodexへ渡すので、対象に機密情報が含まれる場合は注意が必要です。
だから、今回のAGENTS.mdでも停止条件を入れています。対象アプリが違う、作業範囲を越える、認証や権限昇格や外部送信や削除に入る。そういう場面では、自律実行を続けずに止まるべきです。
自律化で重要なのは、何でも進めることではありません。進めてよい範囲を明確にし、その範囲内では毎回余計な確認を挟まずに進めることです。
まとめ

今回の話を一言でまとめるなら、AIエージェントにGUIを触らせるには、操作だけでなく観測を取り直す経路まで必要だということです。
GUI自動化で効くのは観測を取り直せること
今回の発見は、派手な新機能というより、AIエージェント運用の地味な接続部分でした。
Computer UseはGUIを操作できる。AppShotはMacアプリの状態をCodexへ渡せる。しかし、AppShotを人間の手動ホットキーに依存したままだと、操作後の再観測が自律化できない。
そこで、AppShotホットキーを発火する専用ヘルパーを作り、検証済みのkeyDown/keyUp経路だけを残し、AGENTS.mdに運用ルールとして昇格しました。
結果として、Codexは次のような動線を持てるようになりました。
- 対象アプリを前面化する
- AppShotで状態を観測する
- Computer Useで操作する
- 画面変化後にAppShotを再取得する
- 新しい状態に基づいて次の操作を決める
この流れが自然に回るようになると、AIエージェントのGUI操作はかなり実用に近づきます。
たぶん、同じことを考える人はこれから増えるはずです。クリックできるだけでは足りない。見直せること、判断し直せること、そしてその運用ルールが次のセッションにも残ること。AIエージェントのGUI自動化で本当に効いてくるのは、こういう地味な接続部分だと思います。








