sandbox_exec

ボックスでシェルコマンドを 1 つ実行します。タイムアウト付きのフォアグラウンドか、ログファイルに出力するバックグラウンドで動かします。画面が変わると、フォアグラウンドのステップはスクリーンショットと録画を人のために残します。

入力 出力
id、cmd、note、cwd、timeoutSec、background、strict、user(root または tester)、readOnly、tailBytes、readyPort、readyLog、baseline stdout、stderr、exitCode、truncated、durationMs、timedOut、signal、boxTime、note、execId。出力が切り詰められたとき(またはバイナリだったとき)は outputUrl、outputBytes、outputNote、タイムアウト以外のシグナルで終わったときは endReason、コマンドが動いたままのプロセスを残したときは leftoverChildren と hint、環境のアドレスやリンクへの接続が失敗したときは privateEndpointErrors と privateEndpointHint、background なら bgId と logPath、baseline なら下記の比較
  • note(必須):このコマンドの目的を、人が読む言語(ツールの説明に書かれています)で一文で書きます。アプリはコマンドそのものではなくこれを、ボックスが今していることとして表示します。
  • cwd はボックスの作業ディレクトリ /work からの相対パスで、既定はそこです。
  • timeoutSec はフォアグラウンドコマンドを制限します。既定 600、最大 3600。時間切れになると、コマンドは同じプロセスグループで起動したプロセス(& で起動したものも含む)ごと SIGKILL で終了させられ、それまでに出力された内容が、timedOut: true、signal: "SIGKILL"、exitCode -1 とともに返ります。
  • フォアグラウンドの呼び出しにはすべて execId があります(ローカルアダプターが呼び出しを送る前に決めます)。結果が返る前に呼び出しが切れても(接続が切れた、クライアントが待つのをやめたなど)、コマンドは止まりません。終わるか timeoutSec に達するまで動き続け、sandbox_procs の wait を bgId にその execId を指定して呼ぶと、その呼び出しが返すはずだったもの(result:stdout、stderr、exitCode…)が返ります。ですから、先にもう一度実行しないでください。アダプターのエラーには execId とその呼び出しが書かれています。直近 200 件のフォアグラウンドの結果が保存されます。execId を指定した stop で早めに終わらせることもできます。
  • 出力:stdout と stderr はそれぞれ最後の 16 KB を残します(tailBytes でそれより少なく指定できます。1〜16384)。それより多く出力されたときは先頭が切られます。truncated: true になり、outputUrl は完全な出力(両方のストリームを出力された順に。1 時間有効)へのリンク、outputBytes はそのサイズ、outputNote は各ストリームをどれだけ残したかです。ほとんどがバイナリのストリーム(NUL バイト、不正な UTF-8)は返されず、[binary output: N bytes, not shown; …] となり、完全な出力は outputUrl の先にあります。テキスト中の少しの NUL バイトは ␀ か [N NUL bytes] として返ります。出力先は端末ではないので、プログラムは非対話用の形式で出力します。node --test は端末での ℹ fail の行ではなく、TAP(# fail 2、not ok 3 - name)を出力します。
  • タイムアウト以外のシグナルには endReason が付きます:stopped by sandbox_procs stop、OOM killer(ボックスのメモリが足りなくなった。go build -p 2 のように重い処理を同時に動かす数を減らすか、もっと大きいボックスを起動してください)、またはほかのプロセス(kill、pkill)です。
  • cmd は 1 つの JSON 文字列です。ファイルを書くには、引用符付きのヒアドキュメント cat > path <<'EOF' … EOF を使ってください。中身は何も展開されず、バックスラッシュも書いたとおりに残ります(引用符なしの <<EOF は $ とバッククォートを展開します)。長いファイルや多数のファイルは、cmd に入れずに sandbox_sync で送ってください。256 KB を超える cmd は拒否され、非常に長いものはクライアントが壊れた引数を送る原因になり、arguments: … として返ってきます。cmd の別名として command も受け付けます。
  • readOnly: true は、読むだけのフォアグラウンドのコマンド(ログ、ファイル、状態)のためのものです。DISPLAY なしで動き、何も記録されず、人が takeoverUrl でボックスを引き継いでいてほかのコマンドが待たされる間も実行されます。画面を見たり操作したりするためのものではありません。background とは併用できません。
  • フォアグラウンドの呼び出しはシェルが終わった時点で戻り、出力をまだ握っているものはあと最大 3 秒しか待ちません。&、nohup、setsid で起動し、そのとき動いているプロセスはそのままにされます。結果に leftoverChildren: true と hint が付き、プロセスは動き続けますが、何も追跡せず、その後の出力は失われます。先に timeoutSec に達したときは、プロセスグループごと終了させられます。動かし続けるもの(dev server、watcher、データベース、エミュレーター)は、それぞれ background: true の呼び出しで起動してください。sandbox_procs が扱えるのもそれだけです。
  • exitCode は最後のコマンドのものだけなので、失敗したテストを tee にパイプしたり、後ろに別の行があったりすると 0 になります。strict: true はコマンドの前に set -eo pipefail を付けます。exitCode は最初に失敗したコマンドかパイプラインの段のものになり、残りは実行されません。
  • テストの出力が長いときは、関係ないログに失敗が埋もれないよう、結果に要るものだけ残します。たとえば go test ./... 2>&1 | grep -E '^(--- FAIL|FAIL|ok|panic)'(失敗が exitCode に出るよう strict: true か set -o pipefail を付けます)や、go test -json ./... | jq -r 'select(.Action=="fail") | .Package + " " + (.Test // "")'。16 KB を超える出力は先頭から切られ、全体は outputUrl にあります。
  • ボックスだけで使う使い捨てのテスト(偽のデータを作って集計結果を照合する fixture など)は、ボックス内のコピーに直接書いて、実行後に消せます。ローカルのリポジトリには一切触れません:cat > /work/<repo>/internal/rollup/rollup_sbx_test.go <<'EOF' … EOF のあと go test ./internal/rollup -run Sbx、同じコマンドでファイルを rm します。正式なテストにするときだけ sandbox_pull で持ち帰ります。
  • user: "tester" はコマンドを root 以外のユーザーで動かします(uid 1000 が空いていれば 1000、docker グループに所属)。専用の HOME、TMPDIR(/tmp/u<uid>、0700)、GOPATH、npm キャッシュを持ち、最初に使ったときに作られます。uid を切り替えるプログラム(setpriv、会員ごとの uid)や root では動かないプログラムのテストに使ってください。root で動かすと、別の uid に切り替えた子プロセスが root の作った 0700 の一時ディレクトリに入れず、それだけでテストが大量に失敗します。そうしたコマンドの前に毎回、cwd 以下の root 所有のファイル(/work/.sbx を除く)を tester に渡して書き込めるようにするので、cwd には /work 全体ではなくリポジトリを指定してください。既定は root です。/tmp 自体はすべての uid で共有です。テストが別の uid で CLI を動かすと、その uid 用の /tmp/claude-<uid> などが先に作られ、あとでその uid で動かした CLI が使用を拒みます(Temp directory /tmp/claude-1001 is owned by uid 97884)。uid ごとに一時ディレクトリを分け(TMPDIR。Claude Code は CLAUDE_CODE_TMPDIR)、すでにぶつかった場合はそのディレクトリを消してください。
  • background: true はすぐに bgId と logPath を返します。プロセスは呼び出し後も動き続け、出力はそのファイル /work/.sbx/logs/<bgId>.log に書かれるので、別の sandbox_exec で読みます。sandbox_procs は、こうしたコマンドを終わったものも含めて一覧し、1 つの終了を待ち、起動したプロセスごとまとめて止めます。sandbox_status.running.background には動作中のものが logPath 付きで並びます。止めるときは pkill -f や pgrep -f ではなく bgId を使ってください。コマンドはスクリプトファイルから動くので、パターンが自分の sandbox_exec を動かしているシェルに一致することはもうありませんが、コマンドラインにそれを含むほかのプロセスにはすべて一致し、ほかのバックグラウンドジョブも含まれます。バックグラウンドで起動していないプロセスには、プロセス名に完全一致する pkill -x <名前> か kill <pid> を使ってください。
    • プログラムは、端末ではなくファイルやパイプに書く出力をバッファーするので、ログが何分も空のままで、あるとき一度に埋まることがあります。PYTHONUNBUFFERED=1 はすべてのコマンドに設定されます(自分で設定した場合を除く)。パイプラインでは sed -u、grep --line-buffered、stdbuf -oL <command> を使うと、各行が出力されたときにログに届きます。Node の console.log はファイルへもすぐに書かれます。
    • readyPort(サーバーが起動すると 127.0.0.1 か ::1 で接続を受け付けるポート)と readyLog(ログの各行と照合する正規表現。RE2 構文で、"Listening on|ready in" など)は、コマンドの準備ができたことを示す条件です。sandbox_procs の wait を until: "ready" で呼ぶと、固定の sleep の代わりに、どちらかが成り立った時点で戻ります。待つときにこれらを指定することもできます。
    • psbx-step <name> -- <command> [args...] は長いスクリプトの 1 ステップを実行し、その開始、終了コード、所要時間を記録します。その bgId(またはフォアグラウンドの execId)で sandbox_procs を呼ぶと steps として並ぶので、ログに何も出ていなくても、どのステップが実行中で、どれが失敗したかがわかります。終了コードはそのまま返ります(psbx-step build -- make && psbx-step test -- make test は最初の失敗で止まります)。パイプラインは bash -c '…' の中に入れてください。
  • baseline: true は同じコマンドを 2 回、順番に実行します。まず cwd で、次に <dest>-baseline の同じ場所で実行します。<dest>-baseline は sandbox_sync の baseline が dest の隣に置いた以前のツリーです(cwd はそうして同期した dest の中でなければなりません)。両方の出力から失敗したテストを拾い出し(go test の --- FAIL と FAIL <package> の行、node --test の TAP の not ok、node、jest、vitest の ✖、✕、× の行、pytest の FAILED と ERROR)、所要時間とテスト番号を除いて、onlyMine(あなたの変更があるときだけ失敗)、alreadyFailing(両方で失敗)、fixedByMine(変更前だけ失敗)、counts、exitCode と baselineExitCode、そして両方の完全な出力を /work/.sbx/logs に置いた mineLog と baselineLog を返します。名前を 1 つも認識できなかったのに 0 以外で終了した実行があれば、summary にそう書かれます。そのときはログを読んでください。background とは併用できません。
  • プロジェクト自身の依存はイメージに入っていません。Python のプロジェクトは、テストの前にボックスで pip install -r requirements.txt(とプロジェクトのほかの requirements*.txt)を実行してください。pip はシステム全体に入れ、break-system-packages は設定済みです。ボックス内のどこにある使い捨ての Node スクリプトでも、package.json なしで npm install せずに playwright、playwright-core、ws を import できます(import { chromium } from 'playwright'、または require)。それらと Playwright の Chromium、Firefox、WebKit はボックス全体に入っています。これらのブラウザーは Playwright 1.56.0 用です。別のバージョンの @playwright/test を固定したプロジェクトは Executable doesn't exist at /usr/local/share/playwright/… で失敗するので、先にプロジェクトで npx playwright install chromium を実行するか(約 20 秒、共有のブラウザーディレクトリに入ります)、executablePath: '/usr/bin/chromium' で起動してください。別のメジャーバージョンの Node は npx -y node@20 script.js(または node@24、初回約 5 秒)で動きます。イメージには ripgrep(rg)、zip、xxd、bats、bwrap、xclip、python(Python 3.11。Pillow、numpy、boto3、fonttools、brotli 入り。3.12 と 3.13 は toolchains で選択)、ImageMagick(convert、montage)、en_US.UTF-8 ロケール、CJK フォントもあります。git には ID(ParallelSandbox Box。/etc/gitconfig と root の ~/.gitconfig)と safe.directory * が設定済みなので、コミットするテストに準備は要りません。nproc と free -g がこのボックス自身の CPU とメモリを示します。ボックス内に cgroup の割り当てはありません(/sys/fs/cgroup/cpu.max はありません)。ボックスの IBus 入力メソッドは IBUS_ADDRESS ですべてのコマンドに設定されているため、自分で ibus-daemon を起動するテストは env -u IBUS_ADDRESS が必要です。イメージに何があるかは /etc/sbx/manifest.json に並んでいます。
  • コマンドは root として bash -l で動きます。環境にはすでに DISPLAY=:99(仮想ディスプレイ。起動したものは sandbox_shot や takeoverUrl で見えます)、SBX_BOX_ID(ボックス id)、SBX_SCENE_HOST(シーン URL のホスト、key を含むので URL そのものとして扱う)、ボックスに渡したシークレット、そしてボックスの環境が AWS ロールを指定していれば AWS_CONTAINER_CREDENTIALS_FULL_URI と AWS_REGION があります。コンテナには渡さない限り入りません(docker run -e SBX_BOX_ID ...)。各コマンドには PYTHONUNBUFFERED=1、PSBX_JOB_ID(その bgId または execId)、PSBX_STEPS(psbx-step の記録先)も渡されます。
  • 返ってくるものの中ではシークレットの値が隠されます。stdout、stderr、outputUrl の完全な出力(sandbox_procs の cmd と logTail、sandbox_status が並べるプロセスも同じ)では、ボックスに注入したシークレットと /work/.sbx/env の設定の値のうち 8 文字以上のものが **** に置き換わります。複数行の値(PEM 鍵など)は行ごとです。名前からアドレスやモードとわかる設定はデバッグのために見えたままです。HOST、PORT、ENV、TZ、LANG、STAGE と、_HOST、_HOSTNAME、_PORT、_REGION、_ENV、_ENVIRONMENT、_STAGE、_MODE、_LEVEL、_PATH、_DIR、_NAME、_TIMEZONE、_LOCALE で終わる名前です。ただし名前に SECRET、TOKEN、PASSWORD、PASSWD、PWD、KEY、CREDENTIAL、AUTH、PRIVATE、SIGNATURE、SALT、COOKIE、SESSION、DSN も含まれていれば隠されます。8 文字未満の値(true、3000、dev)や、それ以外のもの(psbx-testdb の接続文字列など)は隠されません。ファイルは変わりません。バックグラウンドコマンドのログファイルには本当の値があり(sandbox_exec で読むとまた隠されます)、sandbox_get と sandbox_pull もファイルをそのまま返します。
  • フォアグラウンドのコマンドの実行中(終了後、スクリーンショットを撮るまでの 0.6 秒も含みます)にボックスの画面(DISPLAY=:99、または接続したスマホ)が変わると、そのステップのスクリーンショット 1 枚と、たいていは録画も自動で残ります(例外は下記:スクリーンショットだけのことも、何も残らないこともあります):録画は画面の変化を見つけた時点から始まり(画面を比べるのは 1 秒ごと、スマホ接続中は 2 秒ごとなので、実際に変わった時点より少し遅れます)、スクリーンショットを撮るまで続きます。スクリーンショットはコマンドが終わってから 0.6 秒後に撮られます(その 0.6 秒のうちに次のフォアグラウンドのコマンドを実行すると、そのコマンドが始まる前にすぐ撮られます)。画面が変わらなければ何も残りません。人はアプリでそのボックスの「スクリーンショットと録画」から見られ、各ステップには時刻とあなたの note が付きます。ブラウザやアプリのテストは :99 上で headed で動かしてください(Playwright の --headed、HEADED=1)。そうすれば各ステップで何をテストしたかが人に見えます。画面の記録がそれぞれいつまで残り、ボックスを止めたあと誰が見られるかは、仕上げの表にまとめてあります。詳細と例外:
    • 変化の判定:コマンドの開始前に画面を記録し、そのあと 1 秒ごと(スマホ接続中は 2 秒ごと)にそれと比べます。カーソルの点滅は変化に数えません。コマンドが終わったあと 0.6 秒待ち(次のフォアグラウンドのコマンドがそれより早く始まれば、そこで待つのをやめます)、それまでに変化が見つかっていなければ最後にもう一度比べて、変わっていればスクリーンショットを撮ります。コマンドの間に画面が変わらなければ(ただの npm test など)何も残らず、スクリーンショットもありません。
    • 1 秒たたずに終わるコマンド(xdotool でのクリック 1 回など)は、録画が始まる前に終わります。終了後 0.6 秒以内に画面が変われば、そのスクリーンショットだけが残ります。それより遅く変わると(クリックのあとページの読み込みに数秒かかるなど)何も残りません。クリックの結果を残すには、同じコマンドの中で数秒待ってください。例:xdotool mousemove 400 300 click 1; sleep 2。スマホ接続中はもっと長く待ってください:コマンド開始前の画面はコマンドが始まってから取得され(iPhone は 1 枚に 1〜2 秒かかります)、取得できてから 2 秒ごとに比べるので、録画が始まるのは早くてもコマンド開始の 2 秒後です(iPhone は取得が遅いので、さらに遅くなります)。タップ(artemis-adb tap、psbx-ios tap)による変化が開始前の画面として扱われ、何も残らないこともあります。タップの前後で数秒待ってください。例:sleep 3; psbx-ios tap 200 400; sleep 3。
    • そのステップ自体が動画を撮るかどうかは、画面の変化を最初に見つけた瞬間に一度だけ決まり、あとで変わることはありません。画面の変化を最初に見つけたときにあなたが sandbox_shot で録画していると、コマンド中の画面の変化は、あなたが sandbox_shot で始めた録画にだけ入ります。そのステップ自体には動画がなく(あなたの録画がそのステップの動画になることもありません)、コマンドの終了後に撮るスクリーンショットだけが残ります。あとから動画が足されることもありません。画面の変化を最初に見つけたときに録画していなければ、ステップはいつもどおり独自の動画を撮り、スクリーンショットも撮ります。あなたが sandbox_shot で始めた録画がそのコマンドのステップに入ることはなく、行き先は 2 通りだけです:あなたが record: "stop" で止めたものはアップロードされ、アプリに別のステップ(kind が shot、detail が record のステップ)として出ます。自動で止められたものはアップロードされず、どのステップにもならず、アプリには出ません。その場合、録画中に実行したフォアグラウンドのコマンドについて、人に見えるのはそれぞれのステップのスクリーンショットだけです。ParallelSandbox があなたの録画を自動で止めるのは、早くても、ボックスが最後に使われてから(ボックスに作用するツール呼び出しの終了か復帰。ボックスの状態を参照)まる 1 時間たったあとで、しかもほかに何もボックスを起こしていないときだけです。録画の長さとは関係なく、フォアグラウンドのコマンドの実行中は止めません。自動で止められた mp4 がどこに残り、どう取り出し、取り出したものがいつまで残るかは、sandbox_shot の record の項目にあります。
    • 1 ステップの録画は最長 10 分で、録画が始まった時点(画面の変化を最初に見つけた時点)から数えます。timeoutSec が 600 を超えるコマンドでだけ起こります(録画が始まるのは早くてもコマンド開始の 1 秒後、スマホ接続中はもっと後なので、600 秒以内に終わるコマンドが 10 分録り切ることはありません)。コマンドはそのまま動き続け、録画だけが止まります。録れた 10 分は残り、コマンドの終了後のスクリーンショットも撮られます。
    • background: true のコマンドは録画されません。
    • これらのスクリーンショットと録画は撮影後 7 日間保存されます。ボックスを停止したあとも、アプリの「すべて」→「停止済み(過去 7 日)」から期限内の画像と動画を開けます。ボックスと /work は復元できません。idleTimeoutMin の上限は 10080 分(7 日)です(ボックスの状態)。
    • ボックスを止めたあとも、あなたは取得できます。ただし REST API だけで、対応する MCP ツールはありません:API キーを付けて GET https://api.parallelsandbox.com/v1/boxes/{id}/media を呼ぶと、各ステップを撮られてから 7 日間取得できます(新しい 200 件まで。REST を参照)。各ステップで何をしたかを人に見てもらうときは、sandbox_stop で止めずに sandbox_review で渡してください(仕上げ)。
  • ボックスは x86_64(amd64)で、Intel Xeon のホスト上の Debian 12 です。linux/amd64 のイメージを pull またはビルドしてください。arm64 だけのイメージ(Graviton 向けにビルドしたもの、Apple silicon の Mac で --platform linux/amd64 なしにビルドしたものなど)は exec format error で失敗します。エミュレーションは入っていません。docker run --privileged --rm tonistiigi/binfmt --install arm64 でそのボックスに追加できますが、エミュレーションのコンテナは遅い(試したボックスでは同じ CPU ループに約 6 倍の時間)ので、amd64 でビルドし直すか、マルチアーキテクチャのイメージを push してください(docker buildx build --platform linux/amd64,linux/arm64)。ほかの場所で使う arm64 の成果物(Graviton、arm64 の Lambda)は、ボックスで arm64 のものを動かさない限り、エミュレーションなしでビルドできます。CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build、または RUN がすべてビルドプラットフォームで動く Dockerfile(コンパイルするステージを FROM --platform=$BUILDPLATFORM … にして、そのステージから COPY --from)を docker buildx build --platform linux/arm64 でビルドします。arm64 のステージの RUN はエミュレーションなしでは失敗し、できたイメージはボックスでは動きません(exec format error)。ボックスは Debian 12、glibc 2.36 です。ボックスで cgo を使ってビルドした Go のバイナリ(C コンパイラがあれば既定でそうなり、ボックスにはあります)はこの glibc を必要とするので、古い実行用イメージに入れると GLIBC_2.34 not found で失敗します。CGO_ENABLED=0 でビルドするか、プログラムを動かすのと同じベースイメージの中でビルドしてください。
  • ボックスの画面のブラウザ:
    • 画面は 390 × 844 の縦向きで、orientation: "landscape" で起動したボックスだけが 1280 × 800 です(sandbox_start)。Chromium のウィンドウは最小 500 ピクセル幅なので、縦向きの画面では --force-device-scale-factor=0.78 --window-size=500,1082 --window-position=0,0 で開くとちょうど画面を埋めます。background: true で chromium --no-sandbox --kiosk --force-device-scale-factor=0.78 --window-size=500,1082 --window-position=0,0 --remote-debugging-port=9222 --user-data-dir=/tmp/chrome http://localhost:3000/ を実行します。このときページの CSS 幅は 500 です。ログインしたままのそのページをちょうどスマホ幅で見るには、sandbox_shot を target: "tab" と width で呼んでください(--remote-debugging-port が必要です)。ボックスがそのタブのビューポートを保つので、操作を続けても元に戻りません。ログインしていないページなら target: "url" です。横向きの画面では、この 3 つの代わりに --window-size=1280,800 を使います。コマンドは root で動くので、Chromium には --no-sandbox が必要です。
    • 固定の sleep ではなく条件を待ってください。timeout 60 xdotool search --sync --onlyvisible --class chromium はウィンドウが出たところで戻り、--remote-debugging-port=9222 付きで起動した Chromium は curl -s http://127.0.0.1:9222/json/version が応答すれば CDP で使えます。sandbox_shot も自分で待てます(waitFor、stableMs。target: "tab" は起動中のブラウザを最大 45 秒待ちます)。
    • ウィンドウは openbox が管理します。xdotool windowactivate と getactivewindow が使え、新しいウィンドウはキーボードフォーカスを受け取り、ダイアログ(Electron の dialog.showMessageBox、GTK)はタイトルバー付きで中央に開きます。ほかのウィンドウには枠を付けないので、0,0 から画面と同じ大きさで開いたウィンドウはこれまでどおり画面をちょうど埋めます。openbox 自身のショートカットはなく、すべてのキーがアプリに届きます。マウスポインターは普段は右下の隅にあって画面に写りません。hover を試すときは自分で動かしてください。DBUS_SESSION_BUS_ADDRESS がこの画面のセッションバスを指しているので、デスクトップ通知(Electron の Notification、notify-send)は右上に出ます。
    • 画面の大きさはこの 2 種類だけです。別のサイズで Web ページを見るには、sandbox_shot を target: "url" か "tab" と width、height で使います。デスクトップアプリをたとえば 1440 × 900 で動かすには、自分で画面を開き(Xvfb :100 -screen 0 1440x900x24 を background: true で)、DISPLAY=:100 でアプリを動かし、ffmpeg -f x11grab -video_size 1440x900 -i :100 -frames:v 1 shot.png で撮って sandbox_get で取り出してください。sandbox_shot と人が見る画面は :99 だけです。
    • 要素はロールとアクセシブルネームで探してください(page.getByRole('button', { name: 'Save' }) はアイコンボタンの aria-label にも一致します)。表示テキストや位置では探さないでください。アイコンだけのボタンにはテキストがなく、複数の要素に一致するロケーターは Playwright の strict mode で失敗します。
    • 1 つのブラウザで動かすページは一度に 1 つです。前面にないタブにはフレームが来ないので、そのタブへの page.screenshot はタイムアウトします。同じブラウザの別のタブで作業する前に page.bringToFront()(CDP では Page.bringToFront)を呼び、同時に動かすスクリプトにはそれぞれ専用の --user-data-dir とデバッグポートでブラウザを 1 つずつ用意してください。
    • ユーザー操作が必要なもの(window.open、クリップボード、全画面)には本物の入力が要ります。Playwright の click()、CDP の Input.dispatchMouseEvent、xdotool click です。CDP の Runtime.evaluate で送った element.click() は、呼び出しに userGesture: true を付けない限りユーザー操作にならず、開こうとしたポップアップはエラーなしでブロックされます。
    • 自分の CDP スクリプト用の最小のヘッドあり Chromium:chromium --no-sandbox --remote-debugging-port=9222 --user-data-dir=/tmp/chrome <URL> を background: true で起動し(root で動くので --no-sandbox がないとすぐ終了します)、until curl -sf http://127.0.0.1:9222/json/version >/dev/null; do sleep 0.2; done で起動を待ってから接続します。Playwright の chromium.connectOverCDP('http://127.0.0.1:9222') か、/json/version が返す webSocketDebuggerUrl を使います。
    • ウィンドウはプログラムが終わるまで残るので、background: true で開いて sandbox_procs の stop で閉じます。sandbox_shot の target: "window" は画面に残っているものを windows に列挙します(どれでも xdotool windowkill <id> で閉じられます)。
    • --force-device-scale-factor を付けると --window-size は拡大前のポイント数になり、端数は切り捨てられます。2 倍で 1280 × 800 の画面を埋めるには --window-size=641,401 です。640,400 だと右と下に 1 ピクセルの黒い縁が残り、1280,800 だと画面の 2 倍の大きさのウィンドウを求めることになります。
    • ボックスには GPU がないので、WebGL は既定で使えません(Chromium のログに WebGL1 blocklisted と出て、ページからは WebGL が見えません)。必要なページでは --use-angle=swiftshader --enable-unsafe-swiftshader を付けます。ソフトウェア描画なので結果は正しいものの遅くなります。
    • 音声入力:/opt/psbx/fake-mic/en.wav と /opt/psbx/fake-mic/zh.wav は英語と中国語(北京語)の音声を 1 文ずつ収めています。Chromium を --use-fake-ui-for-media-stream --use-fake-device-for-media-stream --use-file-for-fake-audio-capture=/opt/psbx/fake-mic/en.wav 付きで起動すると、getUserMedia はその録音をマイクとして受け取ります(ループ再生、権限の確認なし)。ほかの文は espeak-ng -v en-us -w /tmp/say.wav "..." で作れます(中国語は -v cmn-latn-pinyin。cmn のままだとピンインの声調の数字を英語で読みます)。
    • スマホを模したページでスワイプするには、CDP の Input.dispatchTouchEvent を touchStart、いくつかの touchMove、touchEnd の順に送ります。gestureSourceType: "touch" の Input.synthesizeScrollGesture はボックスではスクロールしません。
    • ボックスの Chromium は localhost、127.0.0.1、*.localhost、*.parallelsandbox.com に確認なしでクリップボードを使わせ、別の言語のページでも翻訳の案内を出さないので、スクリーンショットや録画に重なりません(managed policy、/etc/chromium/policies/managed/psbx.json)。navigator.clipboard.readText() には今もフォーカスのあるページが必要なので、先に前面に出してください。ほかのオリジンでは権限を与える必要があります(Playwright:context.grantPermissions(['clipboard-read', 'clipboard-write'])。CDP の Browser.grantPermissions はその接続が開いている間だけ有効です)。権限がないと、Chromium がページの上に権限バーを出し、呼び出しは待ったままになります。
    • ページが実際にどのフォントで描かれているかは Chromium に聞きます。要素に対して CDP CSS.getPlatformFontsForNode を呼ぶと、使われたフォントとそれぞれが描いたグリフ数が返ります。unicode-range で分割された Web フォント(多くの CJK フォントや Google Fonts)では document.fonts.check() は当てになりません。Web フォントで描画済みの文字でも false を返すことがあります。
    • Chrome 拡張機能(Manifest V3)は --load-extension=/work/<ext> --disable-extensions-except=/work/<ext> と専用の --user-data-dir で読み込みます。Playwright では chromium.launchPersistentContext にこれらの args と headless: false(DISPLAY=:99 上)を渡します。chrome.runtime.reload() などの開発者向け操作には、そのプロファイルでデベロッパーモードをオンにしておく必要があります(chrome://extensions)。オフだと拡張機能が理由 16777216 で無効になります。service worker は CDP の type service_worker のターゲットとして見え、再起動は chrome://extensions から拡張機能を再読み込みするか、そのターゲットを止めます。ダウンロードの設定は触らないでください。Playwright の acceptDownloads や CDP Browser.setDownloadBehavior の allow は、拡張機能の chrome.downloads.onDeterminingFilename より先にダウンロードを処理してしまうので、そうした動作は既定のままでテストします。
  • ボックス内のプログラムが環境のアドレスやリンクに届かないとき、プログラムから見えるのは接続がリセットされたか閉じられたことだけです。このとき結果に privateEndpointErrors([{target, error, count}]、このコマンドの間のすべての失敗)と privateEndpointHint が付き、コネクタや ParallelSandbox が返した理由が入ります。たとえば the connector could not reach chatbot-dev.svc.local:8000: lookup chatbot-dev.svc.local: no such host(そのサービスは人のネットワークで動いているタスクがない)や the connector "office" of this environment is offline です。これはボックスの向こう側の問題で、コードを変えても直りません(環境)。環境のアドレスでも宣言した名前でもない名前は公開 DNS で引かれます。公開ドメインらしい名前で ERR_NAME_NOT_RESOLVED や NXDOMAIN が出たら公開 DNS にレコードがないということなので、ボックスを疑う前にボックスで dig <名前> を確かめてください。
  • 人が takeoverUrl 経由で接続している間(またはボックスの画面で box の確認を試している間)、sandbox_exec は実行せずにエラー(HTTP 423)を返し(readOnly のコマンドは除く)、人がいつから使っているか、なぜか、遅くともいつ戻るかを伝えます。待って再試行してください。その間も sandbox_get と sandbox_procs は使え、sandbox_shot で画面も撮れます。コードの作業を続けるなら別のボックスを起動して同期することもできます。

すべてのツールとトピックはツールリファレンスに並んでいます。