0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GUI はないはずの macOS Devbox で、スクショもクリックも XCUITest も動作!

0
Posted at

macOS の Devbox(Devin Outposts 経由で Namespace の Apple Silicon マシンを使っています)で、私は最初に「この環境には GUI が無いので視覚検証は無理」と予想しました。

これは誤りでした。Aqua セッションは最初から存在していて、screencapture が失敗していた真因は TCC(macOS のプライバシー保護)の責任プロセス判定でした。権限を通したあとは、スクリーンショット、CGEvent によるマウス・キーボード操作、macOS XCUITest、iOS Simulator まで動きました。

TODO-01-desktop-after-grant.png

この記事は誤診の経緯と、同じ環境で再現するための手順です。

Devbox のセットアップ(Namespace の blueprint 作成から Devin との接続まで)と、そこで見つかった「CI が緑なのに main が壊れていた」話は別記事に書きました。

クラウド上で macOS アプリをビルド・テスト(Devin Outposts × Namespace)

誤診の根拠と、それが間違いだった理由

私が「GUI が無い」と判断した根拠は次の 2 点でした。

launchctl managername          # => Background
screencapture -x /tmp/a.png    # => could not create image from display

いかにも「ディスプレイが無い」ように見えます。ところが実際には Aqua セッションは最初から存在していました。

gui/501 = {
    type = login
    session = Aqua
    ...
}

WindowServer / Dock / Finder はすべて稼働中で、NSScreen も 1280x800 の実ディスプレイを返します。launchctl managername が Background なのは、Devin のシェルが属する管理コンテキストを表しているだけで、マシンに GUI が無いことを意味しません。

同じ疑いを持ったときは、この順で確認するのが早いです。

id -u
launchctl print "gui/$(id -u)" | grep -E 'type|session'
pgrep -af 'WindowServer|Dock.app|Finder.app'
csrutil status
swift - <<'SWIFT'
import AppKit
print("screens=\(NSScreen.screens.count)")
for screen in NSScreen.screens { print("frame=\(screen.frame)") }
SWIFT

gui/$(id -u) が session = Aqua を返すなら、managername が Background でも GUI は存在します。

真因は TCC の「責任プロセス」

screencapture の失敗理由を tccd のログで追うと、こう出ていました。

sudo log show --last 2m --predicate 'process == "tccd"' --info \
  | grep -i -E 'ScreenCapture|Sub:|Resp:'
Handling access request to kTCCServiceScreenCapture,
from Sub:{/opt/namespace/vmguest}
Resp:{... responsible_path=/opt/namespace/vmguest ...}
Service kTCCServiceScreenCapture does not allow prompting; returning denied.

macOS の TCC は、コマンドを実行したプロセス(accessor)ではなく responsible process に対して権限を判定します。ここではそれが Devin のシェルの親である /opt/namespace/vmguest でした。GUI が無いのではなく、許可ダイアログを出せない状況で拒否されていただけです。/usr/sbin/screencapture に権限を付けても意味がありません。

ScreenCaptureKit を使った場合は SCStreamErrorDomain Code=-3801 という形で同じ拒否が出ます。

TODO-06-tccd-log.png

権限を一時的に付与する

この Devbox は SIP が無効(csrutil status が disabled)で passwordless sudo も使えるので、TCC の DB に直接書けます。考え方が分かるように最小の形で書くと、こうなります。

DB="/Library/Application Support/com.apple.TCC/TCC.db"
sudo cp "$DB" /tmp/TCC.db.bak   # 必ずバックアップ

# 拒否されたサービスを、tccd が示した責任プロセスに対して付与する
sudo sqlite3 "$DB" "DELETE FROM access
 WHERE service='kTCCServiceScreenCapture'
 AND client='/opt/namespace/vmguest' AND client_type=1;
INSERT INTO access
 (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier,flags)
 VALUES ('kTCCServiceScreenCapture','/opt/namespace/vmguest',1,2,4,1,'UNUSED',0);"

sudo killall tccd

client_type=1 はパス指定(bundle ID なら 0)、auth_value=2 が許可です。INSERT OR IGNORE を使うと既存の拒否行がそのまま残って効かないので、削除してから挿入します。

サービスと用途の対応は次のとおりです。必要なものだけ付けます。

  • kTCCServiceScreenCapture: screencapture と ScreenCaptureKit
  • kTCCServiceAccessibility: AXIsProcessTrusted() と入力の合成
  • kTCCServicePostEvent: CGEvent でマウス・キーボードイベントを送る

kTCCServiceListenEvent は既定では要りません。イベントを送るだけならタップを張らないので、tccd が ListenEvent の拒否を出したときだけ追加します。

これで screencapture -x が通り、メニューバーも Dock も写った実デスクトップの PNG が取れました。AXIsProcessTrusted() も true になり、CGEvent でカーソルが実際に動きます。

/usr/sbin/screencapture -x /tmp/devbox-after.png
file /tmp/devbox-after.png

これは使い捨ての Devbox だからやっていい話です。自分の Mac や共有マシンで TCC.db を直接いじるのはやめましょう。SIP 無効が前提でもあります。作業が終わったらバックアップから復元してください。

できたこと

権限を付けたあと、実際に次を確認しました。

スクリーンショットは実デスクトップの PNG が取れました(冒頭の画像1)。

CGEvent でクリックし、ダブルクリックで Finder から Calculator.app を起動できました。そのまま 6+6×7 をキー入力して 48 を表示させています。

TODO-02-calculator-48.png

macOS XCUITest も動きます。TextEdit を launch() してウィンドウ検出から click() まで通り、** TEST SUCCEEDED ** で終わりました。

let app = XCUIApplication(bundleIdentifier: "com.apple.TextEdit")
app.launch()
app.activate()
let window = app.windows.firstMatch
XCTAssertTrue(window.waitForExistence(timeout: 5))
window.click()

TODO-03-xcuitest-succeeded.png

iOS Simulator はヘッドレスでそのまま使えて、macOS 側の画面キャプチャ権限にも依存しませんでした。iOS 17.5 / 18.6 / 26.0 / 26.1 / 26.2 の runtime が入っており、simctl boot から simctl io booted screenshot(1179x2556)、iPhone 16e 上での XCUITest まで通りました。

xcrun simctl list runtimes
xcrun simctl boot <device-udid>
xcrun simctl io booted screenshot /tmp/sim.png

TODO-04-ios-simulator.png

最後に自作アプリです。メニューバー常駐でウィンドウを持たないアプリなので、ステータスアイテムをクリックしてメニューの「設定…」を押す、という操作を CGEvent で組み立てました。「Realtime Translator 設定」ウィンドウを開いて、API キー欄が空であることまで画像で確認できました。

TODO-05-app-settings.png

ハマった点

  • 座標系がズレます。screencapture の PNG は 2x Retina(2560x1600)ですが、CGEvent と CGWindowListCopyWindowInfo は論理座標(1280x800、原点は左上)です。PNG のピクセル座標をそのまま渡すとクリックが外れます。画像を見て座標を目分量で決めるのではなく、まずウィンドウを列挙して、報告された bounds から点を選びます。
  • NSEvent.mouseLocation は原点が左下なので、印字される y が CGWindow / CGEvent と上下逆に見えます。
  • ダブルクリックは mouseEventClickState を 1 と 2 に設定して 2 回送る必要があります。これをやらないと、アプリ側がシングルクリック 2 回と解釈して起動しません。
  • ライブなシステムモーダルが前面に居ることがあります(UserNotificationCenter や universalAccessAuthWarn)。CGWindowListCopyWindowInfo で検出して、CGEvent でボタンを押して消す必要がありました。ブラウザに映った過去のスクショをモーダルと誤認しかけたので、必ず現在のウィンドウ一覧で確認します。
  • XCTest から起動した screencapture は責任プロセスが com.apple.XCTRunner になります。シェルに付与した権限は効きません。
  • **TCC の付与はセッションごとにやり直しになります。**セッションを 2 本立てて確認したところ、hostname も kern.boottime も異なり、前のセッションで書いた TCC の付与も残っていませんでした。/opt/homebrew と /Applications は 150GB の永続 volume ではなく OS ディスク側にあり、永続 volume に載っているのは workspace/ と state/ だけです。つまり brew install したものも次のセッションには残りません。恒久化したいなら blueprint の初期化コマンドに入れる必要があります。

Devin の Computer Use は macOS で使えるのか

ここは公開情報が食い違っています。

Devin の一般的な Computer Use のドキュメントは、対応プラットフォームを Linux と Windows とし、macOS は Not supported と明記しています。一方で Devin Outposts の公式発表は、Namespace の macOS Devbox についてこう書いています。

You can't really build iOS apps without a Mac. So we gave Devin one. (中略) And with computer use, Devin can autonomously build, run, and test iOS apps end to end.

Namespace 側の告知も同じ文言で、iOS ゲームを Devin が自分でプレイしてテストするデモが公開されています。つまり「macOS では Computer Use が使えない」と単純に言い切るのは正確ではありません。

そのうえで、私が試した範囲では computer ツールはセッションに一度も提供されませんでした。呼び出そうとすると Unknown tool: computer になります。心当たりを順に潰していったので、その記録を残しておきます。

**依存パッケージ説は外れ。**Outposts の依存関係表は、Chrome / Chromium を「Browser and computer-use features」、ffmpeg を画面録画用として挙げています。しかし Chrome はこの Devbox に最初から入っており(Chrome 145 が Devin の自動化フラグ付きで常駐していました)、ffmpeg を追加インストールしても computer は現れませんでした。

**環境変数も外れ。**Outposts のリファレンスには、desktop stream を有効化する環境変数が定義されています。

DEVIN_OUTPOST_DESKTOP | Optional | Set to true to enable the desktop (VNC) stream.

これを Namespace の blueprint の環境変数に設定したところ、シェルにも worker(devin-remote)のプロセス環境にも true で届きました。**それでも computer は出ません。**なおこの変数は「orchestrator が devin-remote を直接起動する場合」の記述で、devin worker start 側のフラグ一覧には desktop 系がありません。

**組織設定も外れ。**Devin 側の Computer use トグル(設定 → Devin)は有効です。同じ組織の Linux セッションでは computer ツールが普通に使えます。

そして、いちばん示唆的だったのがこれです。同じ macOS セッションに画面録画ツールは配られているのに、computer だけが無い。録画を実行すると、こう返ってきます。

could not launch FFmpeg. Install FFmpeg with `brew install ffmpeg`.

つまり macOS Outpost でも「デスクトップ寄り」のツールが一切遮断されているわけではない。この観測は、「起動時の画面キャプチャ probe(worker のバイナリには desktop probe: screen capture unavailable という文字列と、desktop の可否をサーバへ報告する経路が含まれています)が TCC に拒否されて落ちるので、両方まとめて止まっている」という私の当初の仮説とは噛み合いません。computer の有無はサーバ側でプラットフォーム単位に決まっている(=公式対応表の macOS | Not supported がそのまま効いている)と考える方が、観測とよく一致します。

ただしこれは断定できません。決定打は「worker が起動する前に TCC を通しておくと computer が現れるか」ですが、これは試せませんでした。セッションごとに新しい Devbox が立って権限が引き継がれず、Namespace の blueprint UI には起動時コマンドの欄が無いためです(sessions は CLI の spec file 側にしかありません)。

もう一点、画面録画は TCC を通しても動きません。これはバグに見えます。worker は録画時に次の形で ffmpeg を起動しますが、

ffmpeg -f avfoundation -i 1:none ...

この VM の avfoundation デバイスは [0] Capture screen 0 の 1 つだけで、インデックス 1 は存在しません。

[AVFoundation indev] AVFoundation video devices:
[AVFoundation indev] [0] Capture screen 0

そのため必ず Invalid device index で終了します(exit 251)。手元で 0:none に変えて同じコマンドを流すと、2560x1600・45 フレームの H.264 が問題なく生成できました。デバイス番号の決め打ちが原因です。この 2 点は Cognition に報告済みです。

まとめると、発表文どおりの「full computer use」を素の Namespace blueprint で再現する方法は、公開情報からは分かりませんでした。Namespace 側のドキュメント・ブログ・changelog(2026-07 以降)も洗いましたが、GUI / Aqua / TCC / Computer Use を有効にする設定は一切記載がありません。だからこそ、この記事で書いた「TCC を自分で通して CGEvent を叩く」やり方が今のところ実用的です。

結局どこまでできるのか

やりたいこと 可否
build / unit test 可
CLI 実行 可
スクリーンショット 可(TCC 付与が必要)
マウス・キーボード操作 可(TCC 付与が必要)
macOS XCUITest 可
iOS Simulator + XCUITest 可
Devin の Computer Use 今回は不可(公式には macOS Devbox 対応と案内されているが、computer ツールは一度も提供されなかった)
Devin の画面録画 不可(avfoundation のデバイス番号決め打ちで失敗)
実マイクを使った音声 E2E 不可(音声入力デバイスが 0 件)

音声については system_profiler SPAudioDataType が何も返さず、AVCaptureDevice.devices(for: .audio).count も 0 でした。私のアプリで最後まで残ったのはここだけです。UI は自動操作できるので、「設定画面を開いて API キー欄が空であることを確認する」ようなことは Devbox 上でできてしまいます。

なお、署名証明書が無い環境で scripts/run.sh のような自前ランチャを動かす場合、リポジトリのビルド設定をいじる代わりに、CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO を足す xcodebuild の shim を /tmp に置いて PATH の先頭に入れる、という回避が使えます。ただし未署名ビルドになるので、Hardened Runtime や entitlement の挙動を証明する用途には使えません。起動確認と見た目の確認まで、と割り切る前提です。

手順は Skill として置いてあります

ここまでの試行錯誤を毎回やり直すのは無駄なので、次回以降の Devin が最初から辿れるよう、リポジトリに Skill として固定化しました(PR #44)。

.agents/skills/macos-devbox-gui

SKILL.md は、launchctl managername だけで GUI の有無を判断しない話から、tccd のログで責任プロセスを特定する手順、座標系の罠、ライブなモーダルの片付け方、macOS XCUITest と iOS Simulator の使い方、署名証明書が無いときの回避策、音声デバイスが 0 件だった事実まで、症状から診断・対処・検証の順で並べてあります。scripts/ には次のものが入っています。

  • guievent.swift(list / click / doubleclick / key を持つイベント送出ヘルパー。論理座標でウィンドウを列挙してから押す前提になっています)
  • tcc-temp-grant.sh と tcc-restore-backup.sh(バックアップを取ってから一時付与し、作業後に元へ戻すためのペア)
  • 用途別の SQL テンプレート(screencapture 用、CGEvent 用、XCTRunner 用、対象アプリ用)

この記事に載せた SQL は説明用の最小版です。Skill 側は整合性チェックや失敗時のロールバックまで入れてあるので、同じ構成を試すならこちらを写した方が安全だと思います。

まとめ

launchctl managername が Background を返しても、それは GUI が無い証拠にはなりません。launchctl print gui/$(id -u) で Aqua セッションを確認して、screencapture が落ちるなら tccd のログで責任プロセスを特定する。この順で見れば、macOS Devbox でもスクリーンショットから XCUITest まで届きます。

「GUI が無い環境だから視覚検証は諦める」と書いてしまった過去の自分に、この記事を渡したいところです。

この記事について

ここに書いた作業で、私がやったのは指示を出すことだけです。Aqua セッションの確認、tccd のログから責任プロセスを突き止めたところ、TCC への一時付与、CGEvent での操作、XCUITest と iOS Simulator の検証、Skill としての整理まで、実際に手を動かしたのはすべて Devin です。前の記事で「GUI は使えない」と結論したのも、それを撤回して真因まで辿り着いたのも Devin です。

Computer Use についても、最初は「macOS は非対応」と書いていました。公式発表を突き合わせて誤りに気づき、切り分けて上の結論に直したのも Devin です。

この記事の下書きも Devin が書きました。そのあとのレビューと手直しは私が入れています。

参考リンク

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?