# 実験素材：Claude Codeの許可ルールとフックを30形で試す

記事「Claude Codeの「毎回許可」を減らす設定」で使ったスクリプトと結果です。

- 実施日：2026-10-14
- Claude Code 2.1.291（手元）。一部は2.1.296（`npm i @anthropic-ai/claude-code@2.1.296`）でも再確認
- モデル：claude-haiku-4-5（既定）。haikuが`command`を勝手に外す06と、後から形を差し替えた16・30は、claude-sonnet-5-5で取り直し
- macOS、gh 2.101.0

## 仕組み

`claude -p`に「このコマンドを一字も変えず1回だけ実行して」と頼み、ルールやフックの判定を見ます。

- PATHの先頭に置いた`shim/gh`は、呼ばれたらログに1行書くだけの偽物です。実行されたかどうかは、AIの申告ではなくこのログで判定します
- 例外は23番（`/opt/homebrew/bin/gh`の絶対パス）。偽物を通らず本物のghが動き、`no git remotes found`で失敗します。この失敗メッセージを「実行された」の印にしました
- モデルがコマンドを書き換えた回は捨てて、最大4回までやり直します（`same`が`true`の回だけ採用）
- 判定は「ツール結果のメッセージ」と「ログの有無」で行います。`has been denied`はdenyルール、`blocked by hook`はフック、`requires approval`などは確認待ち

## ファイル

| ファイル | 中身 |
|---|---|
| `run_forms.py` | 30形を1条件で流すハーネス。`python3 run_forms.py D forms.json`のように使う |
| `run_cmds.py` | コマンドと設定の組を自由に流すハーネス（allowルールの表、フックの動作確認など） |
| `forms.json` | 30形の一覧 |
| `shim/gh` | ログを書くだけの偽gh |
| `hooks/hook-v1-naive.sh` | 初版のフック（正規表現1本） |
| `hooks/guard-gh-merge.sh` | 改良版のフック（引用符を外し、展開を検知） |
| `hooks/allow-readonly-gh.sh` | 読み取り系ghだけを`allow`で返すフック |
| `hooks/hook-exit1.sh`・`hook-exit2.sh` | 終了コード1と2の違いを見るフック |
| `hooks/hook-allow-all-gh.sh` | ghなら何でも`allow`で返す、わざと雑なフック |
| `hooks/hook-typo-exit0.sh` | `jq`を打ち間違えた（`jqq`）のに終了コード0で終わるフック |
| `results.json` | 全結果 |

## 条件の記号（`run_forms.py`の第1引数）

| 記号 | 設定 |
|---|---|
| `N` | 何も設定しない |
| `D` | `permissions.deny: ["Bash(gh pr merge *)"]` |
| `K` | `permissions.ask: ["Bash(gh pr merge *)"]`（deny・ask・既定の確認が区別できないので記事では使わない） |
| `H` | フック初版 |
| `H2` | 改良版フック（`onFailure: "block"`つき） |
| `A` | `allow: ["Bash(gh pr view *)"]`と`defaultMode: "dontAsk"` |

環境変数：`REP`（結果ファイルの接尾辞）、`MODEL`、`PMODE`（`bypassPermissions`など）、`CLAUDE_BIN`。

## 動かすとき

スクリプトは作業フォルダを`/tmp/perm`に固定しています。次のように置いてください。

```sh
mkdir -p /tmp/perm/lab /tmp/perm/shim /tmp/perm/out
cp shim/gh /tmp/perm/shim/ && cp hooks/guard-gh-merge.sh /tmp/perm/hook2.sh
cp hooks/hook-v1-naive.sh /tmp/perm/hook.sh
(cd /tmp/perm/lab && git init -q . && mkdir bin && cp ../shim/gh bin/gh)
cd /tmp/perm && cp <このフォルダ>/run_forms.py <このフォルダ>/forms.json .
python3 run_forms.py D forms.json
```

`run_forms.py`は、`D`なら`/tmp/perm/settings_D.json`を作って`claude -p --settings`に渡します。**実際にClaude Codeを起動し、コマンドを実行させます。** `gh`は偽物ですが、`rm`や`git`などの本物は動くので、検証用の空のフォルダでだけ動かしてください。
