experiment-audit について
experiment-audit は、PostHog の実験でバリアントの露出データが収集され、ファネル全体でアトリビューションが保たれ、指標が意図どおりかを確認するスキルです。顧客プロジェクトの確認を必須とし、Slack 向けの監査結果を作成します。信頼できる確認ができない場合は、理由を明示して監査を中止します。
主な機能
- 01監査前に顧客プロジェクトを確認
- 02実験と関連フィーチャーフラグを確認
- 03露出イベントとファネル帰属を検証
- 04指標、バリアント、ターゲティング、配分を確認
- 05信頼できない監査を明示的に拒否
- 06拒否理由と MCP エラーを秘匿化して記録
活用シーン
- 01結果の解釈前に実験設定を検証
- 02バリアント名やトラフィック配分の不一致を発見
- 03フィーチャーフラグのターゲティング問題を調査
- 04監査失敗を後のデバッグ用に記録
MCP 対応
アクティブな PostHog MCP で現在のプロジェクトを確認し、実験、フィーチャーフラグ設定、露出イベントを取得します。顧客プロジェクトを確認できない場合や MCP に接続できない場合、監査を中止します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ skills --skill experiment-auditインストール後、Claude Code で/experiment-auditを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.claude-plugin/plugin.jsonassets/settings.local.jsonscripts/impersonate-audit-shim.shimpersonate-audit.shredact-test.shredact.sh詳細
実験監査
顧客の実験でバリアントデータが実際に収集されていること、ファネルを通じて下流のアトリビューションが維持されていること、そして指標が顧客の想定どおりの内容を測定していることを確認します。顧客がほぼ必ず尋ねる4つの質問ごとに整理した、Slackにそのまま投稿できるレポートを出力します。
ステップ 0 — 実行前にスコープを確認する
このスキルでは、アクティブな PostHog MCP のスコープがあなたのプロジェクトではなく、顧客のプロジェクトに設定されていることを前提とします。必ず次の質問から始めてください。
「今どのプロジェクトにいますか? 最新の実験を5件一覧表示してください。」
回答を次のように解釈します。
- 顧客のプロジェクト。 ステップ 1 に進みます。
- 自分の内部プロジェクト(通常は「PostHog App + Website」、id 2)。偽装セッションでルーティングできていません。「拒否プロトコル」に従って拒否してください。先に進んではいけません。
- MCP 呼び出しが完全に失敗する(応答がない、認証エラー、ツールが利用できない)。「拒否プロトコル」に従って拒否してください。先に進んではいけません。
拒否プロトコル
顧客のプロジェクトを実際に監査できない構造上の条件がある場合は、明示的に拒否して終了してください。次のようなアンチパターンに頼ってはいけません。
- ユーザーが PostHog AI、別の Claude セッション、または顧客自身のツールに貼り付けるためのプロンプト、チェックリスト、手順を生成すること。これは役に立つ対応ではありません。このスキルで監査できなかった理由を隠しながら、作業をユーザーにひそかに押し付けることになります。
- 実データに基づかない「監査レポートの例」のようなテンプレートを作成すること。
- 顧客や製品に関する過去の知識から調査結果を推測すること。
拒否する条件(いずれかに該当する場合はプロトコルを適用)
reason_code | 条件 |
|---|---|
wrong_project | what project am I in? の結果が、顧客のプロジェクト以外のプロジェクト ID だった(通常は自分の「PostHog App + Website」、id 2)。そのプロジェクトが空の場合でも、プロジェクトの識別情報が違う場合は必ずこれを使用します。 |
empty_project | MCP は顧客のプロジェクトに接続している(識別情報が一致する)が、experiment-list の結果が0件。監査に必要な情報がありません。wrong_project との違いは、識別情報が期待どおり一致していることです。 |
mcp_disconnected | MCP サーバーからまったく応答がない。ツール呼び出しがサーバーに到達する前に失敗する(トランスポートエラー、「MCP not connected」、/mcp に posthog エントリがない)。 |
mcp_auth_expired | MCP サーバーは応答するが、成功するはずの呼び出しで 401/403 が返される(偽装セッションの有効期限が切れた、または監査中にトークンが無効化された)。 |
switch_project_failed | switch-project がエラーを返した、または切り替え先のプロジェクトが現在のユーザーのアクセス可能なプロジェクトに含まれていない。 |
experiment_not_found | experiment-list で検索しても、ユーザーが指定した実験が見つからない。同様の名前の実験で代用してはいけません。拒否して確認を求めてください。 |
tool_call_error | MCP サーバーは応答し、認証も有効だが、個別のツール呼び出しで下流のステップを妨げるエラーが発生し、回避できない。「MCP は稼働しているが、この特定の呼び出しが失敗した」場合に使用します。エクスポージャークエリ自体がエラーになった場合(500、タイムアウト、不正なレスポンス)はこのコードを使用します。ただし、クエリが正常に実行され、結果が0行だった場合は拒否理由ではなく、監査結果です(ステップ 3 を参照)。 |
other | 上記のいずれにも該当しない場合。特定のコードが当てはまらない場合のみ使用し、常に該当する具体的なコードを優先してください。 |
拒否時の対応
次の厳密な形式でチャットに拒否内容を出力し、そこで終了してください。
:no_entry: Cannot complete this audit
What I tried: [one line — e.g. "Confirm scope via 'what project am I in?'"]
What went wrong: [one line — e.g. "MCP is scoped to 'PostHog App + Website' (id 2), not the customer's project."]
Why this blocks the audit: [one line — e.g. "Every downstream query would run against your own data, not theirs."]
How to fix: [concrete next step — e.g. "Inside Claude Code, run /mcp → posthog → Clear authentication → Authenticate, with the impersonation browser tab active. Then re-run this audit."]
続けて、$CSM_IMPERSONATE_REFUSAL_LOG(ラッパーによって設定される環境変数。デフォルトは ~/.local/state/impersonate-audit/refusals.log)に、同じ拒否内容を JSONL エントリーとして追加してください。以下の各 <...> プレースホルダーには、自分自身の値を入れてください。プレースホルダーをそのままコピーしてはいけません。トークン形式の文字列が誤分類によってディスクに残ることがないよう、すべての自由記述フィールドは jq に渡される前に $CSM_IMPERSONATE_REDACT の決定論的なレダクターを通ります。
redact() {
# If the redactor is missing, the caller falls through to an empty
# string (bash swallows the non-zero exit inside "$(...)"). The stderr
# note surfaces the reason and no secret can leak — safer than a raw
# write. Prefer running under the wrapper where the env var is always
# set to a real script.
[[ -n "${CSM_IMPERSONATE_REDACT:-}" && -x "$CSM_IMPERSONATE_REDACT" ]] || { echo "redactor unavailable — field will be empty" >&2; return 1; }
printf '%s' "$1" | "$CSM_IMPERSONATE_REDACT"
}
jq -nc \
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg session_id "$CSM_IMPERSONATE_SESSION_ID" \
--arg account "$CSM_IMPERSONATE_ACCOUNT" \
--arg account_name "$CSM_IMPERSONATE_ACCOUNT_NAME" \
--arg audit_file "$CSM_IMPERSONATE_AUDIT_FILE" \
--arg reason_code "<REASON_CODE>" \
--arg tried "$(redact "<one line of what you tried>")" \
--arg went_wrong "$(redact "<one line of what went wrong>")" \
--arg why_blocks "$(redact "<one line of why this blocks the audit>")" \
--arg how_to_fix "$(redact "<one line of the concrete fix>")" \
'{ts:$ts, session_id:$session_id, account:$account, account_name:$account_name, audit_file:$audit_file, reason_code:$reason_code, tried:$tried, went_wrong:$went_wrong, why_blocks:$why_blocks, how_to_fix:$how_to_fix}' \
>> "$CSM_IMPERSONATE_REFUSAL_LOG"
拒否内容のすべての自由記述フィールド(tried、went_wrong、why_blocks、how_to_fix)は、レダクターを通す必要があります。account_name は対象外です(ラッパーの環境変数にすでに設定されている顧客名です)。
$CSM_IMPERSONATE_REFUSAL_LOG が設定されていない場合(ラッパーの外部でスキルが呼び出された場合)は、ログへの書き込みを省略してください。デフォルトのパスにフォールバックしてはいけません。チャットと監査ファイルには、引き続き拒否内容を出力してください。
次に、同じ拒否内容を、人が読める形式(JSON ではありません)で $CSM_IMPERSONATE_AUDIT_FILE に書き込んでください。ファイルを新規作成するか、追記してください。**JSONL ブロックとまったく同じように、4 つの内容行をそれぞれ redact() に通してから書き込んでください。**監査ファイルは永続的で、人が読める成果物です。多くの場合、ノート用リポジトリにコミットされたり、顧客と共有されたりするため、JSONL の姉妹ファイルで慎重に除去したトークン情報を含めてはいけません。ターミナルのバナーがなくてもデバッグログと対応付けられるよう、Session: <id> のフッター行を含めてください。
cat >> "$CSM_IMPERSONATE_AUDIT_FILE" <<EOF
:no_entry: Cannot complete this audit
What I tried: $(redact "<one line of what you tried>")
What went wrong: $(redact "<one line of what went wrong>")
Why this blocks the audit: $(redact "<one line of why this blocks the audit>")
How to fix: $(redact "<one line of the concrete fix>")
Session: $CSM_IMPERSONATE_SESSION_ID
EOF
環境変数が設定されている場合、ログへの書き込みは省略できません。ログエントリーのない拒否は、今後のデバッグで確認できなくなります。
デバッグログ — 後で確認するため、生のエラーを記録する
構造化された拒否ログに加えて、確認できるすべての生の MCP エラーを $CSM_IMPERSONATE_DEBUG_LOG に追記します(デフォルトは ~/.local/state/impersonate-audit/debug.log です)。これは、失敗に遭遇した Jake などが後で確認するログです。拒否ログでは何が失敗したかがわかり、デバッグログではサーバーが実際に何を返したかがわかります。
生のエラーを確認するたびに、JSONL エントリを 1 つ書き込みます。session_id フィールドには、拒否ログに記録したものと同じ値を指定し、2 つのログを結合できるようにします。すべての自由記述フィールドは、jq に渡す前に必ずレダクターに通します。
redact() {
# If the redactor is missing, the caller falls through to an empty
# string (bash swallows the non-zero exit inside "$(...)"). The stderr
# note surfaces the reason and no secret can leak — safer than a raw
# write. Prefer running under the wrapper where the env var is always
# set to a real script.
[[ -n "${CSM_IMPERSONATE_REDACT:-}" && -x "$CSM_IMPERSONATE_REDACT" ]] || { echo "redactor unavailable — field will be empty" >&2; return 1; }
printf '%s' "$1" | "$CSM_IMPERSONATE_REDACT"
}
jq -nc \
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg session_id "$CSM_IMPERSONATE_SESSION_ID" \
--arg account "$CSM_IMPERSONATE_ACCOUNT" \
--arg event "$(redact "<event type — e.g. mcp_error, probe, tool_call_error>")" \
--arg tool "$(redact "<tool name — e.g. experiment-list>")" \
--arg status "$(redact "<HTTP status or transport class — e.g. 401, timeout, tool_not_found>")" \
--arg error_class "$(redact "<short label — e.g. AuthError, ProjectNotFound>")" \
--arg raw "$(redact "<verbatim server error string, capped at ~2000 chars>")" \
'{ts:$ts, session_id:$session_id, account:$account, event:$event, tool:$tool, status:$status, error_class:$error_class, raw:$raw}' \
>> "$CSM_IMPERSONATE_DEBUG_LOG"
許容されるイベント値(イベントの発生ごとに JSONL を 1 行書き込みます):probe(「どのプロジェクトにいるか」の呼び出しとその結果。ただし、記録するのはプロジェクト ID のみで、レスポンス本文全体は記録しません)、予期しないデータが返された呼び出し(空のリスト、予期しない ID など)には tool_call_success、個々の呼び出しの失敗には tool_call_error、トランスポート/認証レベルの失敗には mcp_error を指定します。
レダクションは $CSM_IMPERSONATE_REDACT にパイプして行います。これは LLM の外部で実行される決定論的な Perl スクリプトなので、プロンプトインジェクションを含むエラー本文によって処理を省略させることはできません。次のものを検出します。
Bearer <hex/base64/base64url>—Bearerのプレフィックスは残し、トークン部分を置き換えます。phc_...、phx_...、phs_...、sTOK_...および既知の PostHog トークンプレフィックス — 何が漏れたかの形状がわかるようにプレフィックスを残し、秘密情報の末尾をレダクションします。- JWT(
eyJ<header>.<payload>.<signature>)— 全体を一度に<REDACTED-JWT>に置き換えます。 - Cookie/Set-Cookie ヘッダーの値。
- フレーム化された秘密情報 —
token、code、access_token、refresh_token、authorization、secret、api_keyなどのキーワードに続いて、:/=/空白とトークン形式の値があるものです。このルールはキーワードを基準にするため、保護対象を示しつつ、単独の識別子(フラグキー、クラス名、スラッグなど)はそのまま残します。
注:長さと文字種だけを使った汎用的な検出ルールは意図的に設けていません。このパターンでは正当な識別子まで消えてしまい、人が検索できる状態を保つことがデバッグログの価値につながるためです。実際に新しい形式のトークンが見つかった場合は、その形式を指定したルールを追加してください。
raw フィールドの内容には、次の追加ルールがあります。
- 顧客のクエリ結果を全件貼り付けないでください。目的はサーバーのエラーメッセージを残すことであり、レスポンス本文を残すことではありません。呼び出しは成功したものの、予期しないデータが返された場合は、生の行ではなく、形状の要約(
"返された実験は12件、想定は0件")を記録してください。 rawは約 2000 文字までに制限してください。それを超える場合は切り詰め、末尾に... [truncated]を追加してください。
$CSM_IMPERSONATE_DEBUG_LOG が設定されていない場合は、デバッグログへの書き込みをスキップしてください。デフォルトのパスにフォールバックしてはいけません。
後で拒否ログを確認する
過去の拒否ログを確認するには:jq -c . ~/.local/state/impersonate-audit/refusals.log | tail -20
1 つのアカウントの拒否ログをすべて見つけるには:jq -c 'select(.account == "acme-inc")' ~/.local/state/impersonate-audit/refusals.log
理由ごとに件数を数えるには:jq -r .reason_code ~/.local/state/impersonate-audit/refusals.log | sort | uniq -c | sort -rn
1 つの拒否ログに対応する生のエラーの全履歴を取得するには(session_id で結合):
SID="$(jq -r 'select(.reason_code == "mcp_auth_expired") | .session_id' ~/.local/state/impersonate-audit/refusals.log | tail -1)"
jq -c "select(.session_id == \"$SID\")" ~/.local/state/impersonate-audit/debug.log
Step 1 — 実験の設定を取得する
experiment-list で search を使い、名前から実験を検索します。次に、完全なレコードを取得します。次の情報を記録してください。
- ステータス(実行中/下書き/停止/一時停止)と開始日。
- 関連付けられたフィーチャーフラグのキーと ID。
- バリアントとトラフィックの配分。バリアントの名前は、顧客のコードが読み取る名前と一致している必要があります。バケット分けの不具合は、通常、大文字小文字の違いやタイプミスが原因です。
- ホールドアウト、バケット分けキー(
device_idとuser_idのどちらか)、ensure_experience_continuity。 - エクスポージャーイベント — デフォルトの
$feature_flag_calledまたはカスタムイベント。 - プライマリ指標とセカンダリ指標。アクション ID、イベント名、ブレークダウン、コンバージョンウィンドウ、アトリビューションモードに注意してください。
- テストアカウントを除外する設定。
ステップ 2 — フィーチャーフラグの設定を取得する
リンクされているフラグについて、次を確認します。
- リリース条件 — それぞれ慎重に確認してください。
- 特に注意すべき、よくある落とし穴が 2 つあります:
- ユーザープロパティによる URL ターゲティング(
$current_url = ...)— 現在のページではなく、そのユーザーが最後に閲覧した URL を使用します。定義上、古い情報です。必ず問題として指摘してください。 - パスの一部との完全一致 —
$current_urlは完全な URL(https://host/path)として記録されます。/pathと完全一致させても、条件には一致しません。
- ユーザープロパティによる URL ターゲティング(
- オーディエンスフィルター(デスクトップのみ、地域、コホート)。URL ではなく、ユーザープロパティまたはグループプロパティが使われていることを確認してください。
- ロールアウトの割合と、スーパー条件の有無。
- フラグの
ensure_experience_continuity設定(これは実験レベルの設定を上書きします)。
ステップ 3 — 曝露が発生していることを確認する
実験の開始日以降について、フラグキーに対応する $feature_flag_called イベントを取得します。
- 合計数。経過時間に対して疑わしいほど少ない場合は、詳しく調査してください。
$feature_flag_responseごとに内訳を確認します。バリアント名(例:control/test)間で、ほぼ 50/50 に分かれるはずです。偏りが約 5% を超え、データ量も十分にある場合は、サンプル比率の不一致として指摘してください。- ほとんどのイベントで
$feature_flag_responseがfalseを返す場合、ユーザーは実験にまったく割り当てられていません。つまり、リリース条件によって対象から除外されています。これは「実験の曝露数が 0 と表示される」最も一般的な原因です。 - 生イベントの行を 5 件抽出して確認します。
$current_url、$device_type、$feature_flag、$feature_flag_response、distinct_idに注目してください。
ステップ 4 — 下流へのアトリビューションを確認する
各指標イベント(CTA クリック、登録ページの閲覧、登録完了、コンバージョン)について、次を確認します。
- サンプル行を取得し、実際のバリアント値(
controlまたはtest)を持つ$feature/<flag-key>プロパティが含まれていることを確認します。値がfalseだったり、プロパティが欠落していたりしてはいけません。 - アクションベースの指標:アクションフィルターを確認します。バリアントによって異なる DOM ID がレンダリングされる場合、アクションはすべての ID に一致する必要があります。一致しないと、一方のバリアントのイベント数が実際より少なく表示されます。アクションの URL フィルターは、開発用プレビューではなく、本番ページに一致させてください。
- 指標のスコープ:指標の範囲が広すぎる場合(例:
/signupを含む任意の$pageview)、流入元にかかわらず、グローバルなトラフィックを両方のバリアントに計上します。$feature/<flag-key>プロパティまたはセッション開始時のパス名によるスコープ設定を提案してください。
ステップ 5 — ID の連続性を確認する
ドメインをまたぐ引き継ぎ(Webflow → app、マーケティング → プロダクトなど)では、通常アトリビューションが途切れます。
- ファネルの最終ステップ(例:登録完了)に到達したユーザーを 5〜10 人選び、イベントのタイムラインを取得します。
- そのユーザーに、実際のバリアント値を持つ先行の
$feature_flag_calledイベントがあることを確認します。 - 引き継ぎ時に
$identifyが発火することを確認します。ユーザーに$identifyイベントが一度もない場合、匿名のデバイスプロファイルは認証済みユーザーに結び付かず、フラグが正しく発火していてもバリアントのアトリビューションは機能しません。 - バケット割り当てキーと
ensure_experience_continuityの設定を組み合わせたとき、バケットが再割り当てされないことを確認します。連続性を有効にせずdevice_idでバケット割り当てすると、新しいデバイスの同一ユーザーは新規ユーザーとして扱われます。
ステップ 6 — 下流のコンバージョン指標(トライアルの有効化/購入など)
顧客は、下流(アプリまたはデータウェアハウス)に主要なコンバージョンイベントを持っていることがよくあります。
- イベントスキーマを検索し、想定されるイベント名を探します。いくつかの別名も試してください(
plus_trial_activated、trial_started、subscription_created)。 - 見つからない場合は、
external-data-sources-listを使ってデータウェアハウスのデータソースを探します。よくある例は、accounts.trial_started_atのような Snowflake/Postgres のテーブルです。 - よりシンプルな方法を推奨します。アプリから有効化時にサーバーサイドイベントを送信します。データウェアハウスの結合より簡単で、シグナルも早く、スキーマ変更の影響も受けにくくなります。
- 代替案として、データウェアハウスのテーブルを実験指標に直接使用できます(ファネル指標とトレンド指標に対応しています)。
ステップ 7 — 発見内容にかかわらず、指摘すべきよくある落とし穴
お客様の実際の設定では、ほぼ必ず次のいずれかが見られます。
- 人物プロパティの URL ターゲティング(この用途では常に誤りです)
- 完全な URL を格納した人物プロパティに対する完全一致演算子(一致することはありません)
- 本番環境では発火しない開発用 URL/セレクターに紐づいたアクション指標
- 両方のバリアントに同じように成果を計上するグローバル指標(「あらゆるサインアップ完了」など)
- マーケティングドメインからプロダクトドメインへの引き継ぎ時に
$identifyがない ensure_experience_continuityなしでdevice_idによるバケット分けを行っているため、セッション間で再バケット分けされる- デフォルトの 14 日間のコンバージョン期間が、後続のコンバージョンイベントには短すぎる
- 社内ユーザー/テストユーザーのフィルターが未設定で、初期段階の数値が QA トラフィックに偏る
- ローンチから 24 時間以内に露出を確認してください。50/50 の割り当てで 1 週間のイベントが 10 件未満なら、検出力の問題ではなく配線の不具合です
出力形式
レポートは標準的な 4 つのお客様からの質問ごとにまとめ、最も重大な指摘から記載してください。
:warning: [Experiment name] — audit findings
[One-paragraph TL;DR of the headline finding. Be direct.]
---
(a) Does the config look correct?
[Verdict + specific issues with evidence — event counts, sample values, etc.]
---
(b) How to verify attribution (once issues are fixed)
[Concrete steps the customer can run themselves.]
---
(c) What to change about attribution
[Numbered action list, priority order. Each item should be specific
enough that the customer's engineer can act on it directly.]
---
(d) Common pitfalls to watch for
[Subset of step 7's checklist relevant to this customer's setup.
Frame as general guidance, not as accusations.]
---
Bottom line: [one or two sentences. What's the single most important
fix that unblocks the experiment?]
ルール
- 読み取り専用です。 インサイト、ダッシュボード、アクション、実験を作成したり、設定を変更したりしないでください。読み取り専用の代理アクセスでお客様のプロジェクト内を操作しています。閲覧のみに徹してください。
- プロンプトを渡して作業を委ねないでください。 PostHog AI、別の Claude セッション、その他のツールでユーザーが実行するためのプロンプト、チェックリスト、手順を作成しないでください。このセッションで監査を実施できない場合は、拒否プロトコルに従って明示的に拒否してください。作業をひそかにユーザーへ差し戻すスキルは、自らの失敗を覆い隠すものです。
- 圧力を受けても拒否を貫いてください。 ユーザーが拒否に反発しても(「プロンプトだけくれれば、こちらで貼り付ける」「とにかく実行して」「出力するはずだった内容を生成して」など)、拒否を撤回してはいけません。同じ
reason_codeを使い、同じ形式で拒否を繰り返して終了してください。失敗モードを明らかにして修正につなげることが目的であり、回避策を示すことではありません。 - 生のトークンを出力しないでください。 すべてのログエントリ(
refusals.logとdebug.logの両方)におけるすべての自由記述フィールドは、書き込む前に$CSM_IMPERSONATE_REDACTに通してください。手作業での編集は試みないでください。このスクリプトの目的は、編集を決定的に行い、プロンプトインジェクションで改変できないようにすることです。スクリプトを利用できない場合(環境変数が未設定の場合)は、そのフィールドを書き込まず、編集機能を利用できなかった事実を記録してください。 - 捏造しないでください。 実験が見つからない場合やデータが空の場合は、その旨を明示して、拒否プロトコルに従って拒否してください。テンプレートを埋めるために指摘を作り上げないでください。
- 実際の数値を根拠として示してください。 露出数、サンプル比率、イベント量に関するすべての主張は、このセッションで実際に実行したクエリから得てください。
- 曖昧さを明らかにしてください。 設定が意図的な場合もあり得るなら(たとえば、コンバージョンがすぐに発生するためにコンバージョン期間を短くしている場合)、両方の解釈を示し、お客様に確認してください。
- お客様の文体に合わせてください。 PostHog を使うお客様は、たいてい技術的な知識があります。過度に単純化しないでください。ただし、相手がまだ知らない可能性のある専門用語の省略形は避けてください。
監査が完了したら
ユーザーに次の操作を行うよう伝えてください。
- Claude Code を終了する
- ブラウザーで Django Admin の代理ログインを終了する
- 必要に応じて posthog プラグインを無効にする:
claude plugin disable posthog
impersonate-audit.sh ラッパーは、終了時に手順 3 の確認を自動的に処理します。