1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

1
Posted at

前回、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サービスを量産しています

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?