何に困ったか
Claude Code にブラウザ拡張(Claude in Chrome)を使わせて、フォームへの入力を任せていました。
ある日、「先に送っちゃおう」と私が言ったのを許可と受け取って、Claude が送信ボタンを押してしまいました。私は承認したつもりがありませんでした。
CLAUDE.md に「送信は自分が押す」と書く方法もあります。ただ、文章のお願いは守られないことがあります。
そこで、外に出る最後のボタンのクリックだけを、PreToolUse フックで機械的に止めることにしました。入力・下書き保存・ページ移動は止めません。
この記事では、実際に動かしているフックと、手元で実行して確かめた結果、止められなかった抜け道を書きます。
環境
- Claude Code(CLI)、Python 3
- ブラウザ操作は Claude in Chrome の MCP ツール(
mcp__claude-in-chrome__computerなど) - フックは
~/.claude/hooks/boss-button-guard.pyに置き、~/.claude/settings.jsonから呼ぶ
設定: settings.json
PreToolUse の matcher に、クリックやキー入力ができるツールを並べます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__claude-in-chrome__computer|mcp__claude-in-chrome__browser_batch|mcp__claude-in-chrome__javascript_tool",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/boss-button-guard.py",
"timeout": 10
}
]
}
]
}
}
フックは終了コード 2 で終わると、そのツール呼び出しを止め、標準エラーに出した文章を Claude に返します。
フックの中身
判断に使うのは、computer ツールの引数にある action_summary(これから何を押すかの短い説明)です。
Claude は押す前にこの欄へ「何のボタンか」を書くので、その言葉で判定します。
import json, re, sys
data = json.load(sys.stdin)
tool = data.get("tool_name", "")
inp = data.get("tool_input", {}) or {}
# 押したら外に出る・取り消せないボタンの言葉
FINAL = re.compile(r"送信|送る|投稿|公開する|公開し|応募|購入|申し込|申込|予約設定|更新する|返信する|フォローする|いいね|リポスト|提案する|契約する|署名|承諾|同意する|submit|send|post|publish", re.I)
# 押してよいもの(外に出ない)
SAFE = re.compile(r"下書き|公開に進む|公開設定へ|プレビュー|入力|貼り付け|クリックする前|開く|選ぶ|閉じる")
CLICKS = {"left_click", "double_click", "triple_click", "key", "right_click"}
def bad_computer(i):
if i.get("action") not in CLICKS:
return None
s = i.get("action_summary", "") or ""
if i.get("action") == "key" and not re.search(r"Return|Enter|cmd\+Return|ctrl\+Return", i.get("text", "") or "", re.I):
return None
if FINAL.search(s) and not SAFE.search(s):
return s
return None
def bad_js(t):
# JS で押すボタンを、押す言葉で見つけているもの
if re.search(r"\.click\(\)|dispatchEvent\(|requestSubmit\(|\.submit\(\)", t) and re.search(
r"送信|投稿|公開する|応募|購入|申し込|更新する|返信|フォロー|予約設定|提案する|契約", t
):
return "JavaScript でのクリック"
return None
why = None
if tool.endswith("__computer"):
why = bad_computer(inp)
elif tool.endswith("__javascript_tool"):
why = bad_js(inp.get("text", "") or "")
elif tool.endswith("__browser_batch"):
for it in inp.get("actions", []) or []:
n, i = it.get("name", ""), it.get("input", {}) or {}
if n == "computer":
why = bad_computer(i)
elif n == "javascript_tool":
why = bad_js(i.get("text", "") or "")
if why:
break
if not why:
sys.exit(0)
print(f"止めた: 「{why}」は外に出る最後のボタンなので、人間が押す。入力までで止め、「〇〇のタブの△△を押してください」と頼む。", file=sys.stderr)
sys.exit(2)
ポイントは3つです。
- 止める言葉(FINAL)と通す言葉(SAFE)の2段にする。「下書き保存」は「保存」でも外に出ないので、SAFE に入れて通します
-
browser_batch(複数の操作をまとめて渡すツール)の中身も見る。まとめた操作の中にクリックが混ざっていても止まります -
Enter キーも対象にする。フォームは Enter で送れるので、
keyのReturn/Enterも同じ言葉で判定します
動かして確かめた結果
標準入力に JSON を渡して、終了コードを見ました(echo '<JSON>' | python3 boss-button-guard.py; echo $?)。2026-11-05 に実行した結果です。
-
action_summaryが「「投稿する」ボタンを押す」の左クリック → 終了コード 2(止まる) - 「下書き保存を押す」 → 0(通る)
- 「公開に進むを押す」 → 0(通る。この画面は外に出ない)
- 「本文欄をクリックする(投稿の入力)」 → 0(通る。「投稿」という言葉があるが、SAFE の「入力」で通る)
-
javascript_toolでdocument.querySelector("button").click(); // 送信→ 2(止まる) -
keyのReturnで、説明が「Enterで送信」 → 2(止まる) - 「予約設定を押す」 → 2(止まる)
止まったとき、Claude には「止めた: 〇〇は外に出る最後のボタンなので、人間が押す」という文が返り、入力までで手を止めて、押してほしいボタンを私に頼む動きになりました。
つまずき・抜け道
1. 言葉で判定しているので、説明の書き方で通ったり止まったりする
最初、ボタンの説明に「投稿」と書いただけで、本文を入れる欄のクリックまで止まりました。
説明を「本文欄をクリックする(入力)」のように言い換えると通ります(上の4つ目)。
逆の問題もあります。FINAL にない言葉で説明すれば、本当は送信ボタンでもすり抜けます。
このフックは「説明文を正直に書く」ことが前提です。完全な壁ではなく、うっかり押すのを防ぐ柵です。
2. 例外は、書式を固定して1つだけ
予約の時間が画面に出る「予約設定」のように、取り消せるボタンだけは Claude に押させたい場面がありました。
私は例外を、画面に出た日時の文をそのまま写した説明にだけ合う正規表現で1つだけ作りました。
X_SCHEDULE = re.compile(r"^X の予約設定を押す\(2026年\d{1,2}月\d{1,2}日\([月火水木金土日]\)の午[前後]\d{1,2}:\d{2}に送信されます\)$")
left_click で、説明がこの文面に完全一致したときだけ通します。上の「予約設定を押す」は一致しないので止まります。
「すぐ出る」ボタン、返信、JS のクリックは例外にしていません。
3. 口頭の許可より、仕組みの止めのほうが強い
「いいよ、送っちゃおう」と私が言っても、このフックは止めます。
許可を広げたいときは、会話で言うのではなく、フックの例外として書き直すことにしました。
まとめ
- 外に出る最後のボタンは、CLAUDE.md の文章ではなく、PreToolUse フック(終了コード 2)で止めた
- 止める言葉と通す言葉の2段にして、
browser_batchと Enter キーも見る - JS のクリックも止まる。ただし説明文の言い方次第で抜けられるので、これは柵であって壁ではない
- 例外は、画面の文を写した正規表現に完全一致するものだけ
動かない場合の確認は、echo '<JSON>' | python3 boss-button-guard.py; echo $? で終了コードを見るのが早いです。
参考
- Claude Code の Hooks(PreToolUse、終了コード 2 でツール呼び出しをブロック): https://docs.claude.com/en/docs/claude-code/hooks