前回、Wi-Fiが5時間死んでいた朝の話を書きました。今回は同じ「無人環境を手作業から解放する」系のネタで、Claude CodeのComputer Use承認ダイアログを自動で押すデーモンの話です。
困りごと:「Allow for this session」を毎回手で押す
Claude CodeでComputer Use(画面操作)を使うと、Computer Use wants to control these apps という確認ダイアログが出ます。「Allow for this session」を選んで Enter を押せば通りますが、これがセッションを跨ぐたびに何度でも出る。設定で恒久化できないか、バイナリの strings を当たって確かめました。
computerUseMcpState.allowedApps(セッション内メモリ)にしか積まれず初期値は空
settings.json / ~/.claude.json に事前付与キーは無い
dialog kind=computer_use_approval(requestDialog系)で PermissionRequest hookも通らない
bypassPermissionsModeAccepted: true でも出る
claude 2.1.266 のバイナリを strings で追った実査記録がこれです。許可状態は computerUseMcpState.allowedApps というセッションメモリにしか保持されず、プロセスを跨いだ瞬間に消えます。同じ調査で恒久化できた設定も見つかりました。~/.claude.json の bypassPermissionsModeAccepted: true と projects[*].hasTrustDialogAccepted: true(85件一括)は効きますが、これは「危険操作の確認」を消すだけで、Computer Use承認ダイアログとは別物です。つまり設定側でどうにかなる余地がない、というのがstringsで確定した結論でした。
allow ルール文字列の途中に *(grepの正規表現 [^}]*)を混ぜると「wildcard before the rest」警告が出て弾かれます。ワイルドカードは末尾に置くのが安全です。
設定で無理なら、画面を見て人間の代わりにEnterを押すしかありません。そこで書いたのが ~/.claude/scripts/cu_dialog_autoallow.py です。
実装:全Terminalタブをポーリングして誤爆を防ぐ
やっていることは単純で、Apple Terminalの全タブを2秒ごとにポーリングし、ダイアログ文字列が見えたら \r を送るだけです。ただし「見えたら即Enter」だと事故ります。実装は誤爆防止に寄っています。
READ = f'''
tell application "Terminal"
set out to ""
repeat with w in windows
set k to count of tabs of w
repeat with i from 1 to k
set c to contents of tab i of w
set out to out & (tty of tab i of w) & "{SEP}" & c & "{SEP}"
end repeat
end repeat
return out
end tell
'''
history プロパティを使うと1タブあたり600KBもあって重いので、可視画面(contents of tab i of w)だけを読みます。repeat with t in tabs で回して contents of t を取ろうとすると、タブ自身のオブジェクトが返ってくる罠があり、tab i of w の形で明示的にインデックスアクセスする必要がありました。
誤爆防止の核心は、カーソルがどちらの選択肢を指しているかを見てから撃つことです。
def selected_is_allow(text):
# the highlighted option line starts with the pointer glyph
tail = text.split("Enter to confirm")[0]
lines = [l.strip() for l in tail.splitlines() if l.strip()]
for l in reversed(lines[-6:]):
if l.startswith("❯") or l.startswith(">"):
return "Allow for this session" in l
return False
❯(またはASCII環境向けの >)で始まる行がポインタ行で、そこに「Allow for this session」が含まれているかだけを見ます。デフォルトカーソルがDeny側にあるダイアログにこのままEnterを送ると拒否になるので、ここを飛ばすと逆効果になります。
if "Computer Use wants to control these apps" in text and "Enter to confirm" in text:
now = time.time()
if now - last_sent.get(tty, 0) < 8:
continue
if not selected_is_allow(text):
log(f"{tty}: dialog visible but cursor not on Allow; skipping")
last_sent[tty] = now
continue
res = press_enter(tty)
Enterの送り方も、フォーカスを奪わずに済む形にしています。
def press_enter(tty):
scpt = f'''
tell application "Terminal"
repeat with w in windows
repeat with t in tabs of w
if tty of t is "{tty}" then
do script "" in t
return "sent"
end if
end repeat
end repeat
return "notfound"
end tell'''
do script "" in t はそのタブのttyへ改行1バイトを書き込むだけで、ウィンドウをアクティブにしません。tty単位で8秒デデュープし、fcntl.flock で多重起動を防いでいます。
踏んだ罠:launchdから起動すると無言でhangする
最初は launchd の定期ジョブから起動しようとしました。結果、python3 から Terminal へのAppleEventが TCC(Automation) で無言hang します。プロンプトすら出ず、ただ固まる。実際のログがこれです。
2026-09-12 15:32:35 osascript timeout
2026-09-12 15:32:57 osascript timeout
2026-09-12 15:33:19 osascript timeout
2026-09-12 15:33:41 osascript timeout
2026-09-12 15:34:03 osascript timeout
2026-09-12 15:34:25 osascript timeout
2026-09-12 15:34:47 osascript timeout
2026-09-12 15:35:09 osascript timeout
2026-09-12 15:35:31 osascript timeout
22秒間隔で osascript timeout が延々と続いているのが分かります。launchdから起動したプロセスはユーザーTCCコンテキストの外にいるため、Terminalへの制御要求が承認ダイアログすら出せず握りつぶされる、という挙動でした。
TCC(Automation)は「許可ダイアログが出て拒否される」より厄介で、プロンプトも出さずに無言でhangします。launchd経由のAppleEvent送信は、この型の失敗を疑うのが早道です。
回避策:Terminalの子プロセスとして起動する
回避策は、Terminal自身の子プロセスとして起動することでした。同一アプリの子プロセスからのAppleEventはTCCの許可要求自体が発生しません。~/.zshrc の末尾にこう書いています。
# Claude Code computer-use「Allow for this session」を自動Enter(Terminal起点で起動=TCC不要・launchd起点はTCCで固まる)
if [[ "$TERM_PROGRAM" == "Apple_Terminal" ]] && ! pgrep -qf cu_dialog_autoallow.py; then
(nohup /usr/bin/python3 ~/.claude/scripts/cu_dialog_autoallow.py >/dev/null 2>&1 &)
fi
TERM_PROGRAM でApple Terminal起点のシェルだけに絞り、pgrep で二重起動を止めてから nohup でバックグラウンド常駐させます。新しいタブを開くたびにこのブロックは通りますが、pgrep ガードで実際に立ち上がるプロセスは1つだけです。切り替え後は start pid=... がログに残り、実際にダイアログへ \r を送れています。
2026-09-12 15:36:16 /dev/ttys010: CU dialog -> Enter (sent)
2026-09-12 15:36:18 /dev/ttys011: CU dialog -> Enter (sent)
2026-09-12 15:36:26 /dev/ttys010: CU dialog -> Enter (sent)
踏んだ落とし穴
-
launchd起点だとAppleEventがTCCで無言hang → Terminalの子プロセスとして
.zshrc末尾からnohup起動 -
historyプロパティは1タブ600KBで重い → 可視画面のcontents of tab i of wだけ読む -
repeat with t in tabs; contents of tはタブ自身を返す罠 →tab i of wで明示インデックスアクセスする -
カーソル位置を見ずにEnterを送ると誤爆(Deny側でも押してしまう) → ポインタ行
❯を解析してAllow確定時だけ送る -
AppleScript側の一時的な接続断・構文エラーがログに出る(
実行エラー: 接続が無効です (-609)/syntax error ... (-2741))→ 単発timeoutは無視してポーリングを継続する設計にしておく(1周ロストしても2秒後にリトライされる)
最後の点は実測ログに残っています。09-13朝と09-16昼に接続断・構文エラーが立て続けに出ていますが、いずれも数分後の再起動サイクルで自然に復帰し、実際のダイアログ承認(CU dialog -> Enter (sent))は09-16 23:30にも記録されています。なお実ダイアログでの\r到達は、別セッションがCUロックを握っていたタイミングでは未実測で、偽ダイアログ画面でのend-to-end確認に留めています。
まとめ
- Computer Use承認は
strings調査の結果、設定での恒久化ルートが存在しない(セッションメモリのみ・PermissionRequest hookも通らない) - 回避は可視画面ポーリング+カーソル位置チェックで誤爆を防ぎながらEnterを代行する設計
- launchd起点はTCCで無言hangする。Terminalの子プロセスとして起動するのが唯一の回避策
- 単発の接続断・構文エラーはポーリングループが握りつぶして自己回復する
次回は、このAppleScript監視デーモンの兄弟にあたるTerminal窓の一発整列の話を書きます。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
- 制作物・記事は bokuwalily.com にまとめています🖥️
- AIで「寝てても回る仕組み」を作って月120万にした話は noteの有料記事 に💰
- OSS: github.com/bokuwalily 🐙
- 最新情報・お問い合わせは X @bokuwalily へ🌍
- AI導入・自動化の相談と実装テンプレ7本の配布は 公式LINE から💬
皆さんの ❤️ やシェアが励みになります!