🖱️ Computer Use承認ダイアログを自動で押す ― Terminal監視デーモンの作り方 — リーダー×
🖱️

Computer Use承認ダイアログを自動で押す ― Terminal監視デーモンの作り方

#claudecode#applescript#macos#automation#python2026-09-19 · 約8

前回、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.jsonbypassPermissionsModeAccepted: trueprojects[*].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サービスを量産しています

皆さんの ❤️ やシェアが励みになります!