---
name: permission-hooks
description: Diagnoses and fixes PermissionRequest hook issues when Bash triggers unexpected approval prompts. When called with arguments, treat the argument as the diagnostic target command — never execute it. Use when commands are blocked, user mentions "permission"/"hooks"/"承認", or adding auto-approval patterns.
allowed-tools:
  - Bash(python3 -m unittest *)
  - Read
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "$HOME/.claude/hooks/approve-skill-bash.py $HOME/.claude/skills/permission-hooks/SKILL.md"
---

# Permission Hooks 整備

Claude Code の PermissionRequest フック（自動承認ロジック）を診断・修正する。

## 引数の解釈

`/permission-hooks <コマンド>` のように引数付きで呼ばれた場合、その引数は **承認を求められたコマンド** である。

- **引数のコマンドを Bash で実行してはならない**。文字列として扱い診断のみ行う。
- まず以下を出力し、診断ワークフローのステップ 2 から開始する:

```
診断対象コマンド（非実行）: <引数のコマンド全文>
```

- 第一アクション: `config/claude/settings.json` を Read し `permissions.allow` を確認する。

引数なしの場合は、ユーザーに状況を確認してからワークフローを開始する。

## ツール利用

- **allowed-tools**: Read と unittest のみ。Edit は意図的に除外し、修正時にユーザー確認を求める。
- **修正時**: ソースファイルの編集には Edit ツールを使用する。Bash でのファイル書き込み（`echo > file` 等）は禁止。

## 対象ファイル

ソースは dotfiles リポジトリ内（Nix 管理）:

| 用途 | ソースパス |
|------|-----------|
| 複合コマンド自動承認 | `config/claude/hooks/approve-safe-commands.py` |
| スキル固有 Bash 承認 | `config/claude/hooks/approve-skill-bash.py`（各 SKILL.md の hooks で呼出） |
| safe-commands テスト | `config/claude/hooks/test_approve_safe_commands.py` |
| skill-bash テスト | `config/claude/hooks/test_approve_skill_bash.py` |
| 許可/拒否リスト | `config/claude/settings.json` → `permissions.allow` / `permissions.deny` |

## アーキテクチャ

### 承認判定フロー

```
コマンド発行
  → permissions.deny にマッチ？ → 拒否
  → permissions.allow にマッチ？ → 承認（フック不要）
  → スキルがアクティブ & SKILL.md に PreToolUse hooks がある？
    → approve-skill-bash.py がスキルのパターンで判定 → 承認
  → PermissionRequest フック実行:
      1. approve-safe-commands.py
    → allow を返す？ → 承認
    → exit(0)？ → ユーザーに承認確認を表示
```

### approve-safe-commands.py の判定パス

最新の判定パスは `approve-safe-commands.py` を Read し、末尾の if 条件で確認すること。以下は主要な判定カテゴリ（検索キーは参考）:

| カテゴリ | 概要 | 検索キー（参考） |
|----------|------|------------------|
| サフィックス除去 | Claude Code 付加分を前処理で除去 | — |
| AI CLI コマンド検証 | codex/gemini/cursor-agent の SQ パーサー検証 | `_is_safe_ai_cli` |
| npm audit 読み取り専用 | fix を除外し読み取り専用操作のみ許可 | `_SAFE_NPM_AUDIT` |
| help/version 判定 | `--help`/`--version` で終わるコマンド | `_SAFE_HELP_VERSION` |
| 構造化コミット等 | git commit heredoc / for-wc ループ | `PATTERNS` |
| 読み取り専用複合チェーン | RO コマンドの &&/\|\| チェーン | `_is_safe_readonly_compound` |
| git 読み取り専用パイプ | git show/diff/log のパイプライン | `_is_safe_git_readonly_pipeline` |
| gh api GET パイプ | gh api 読み取り専用リクエストのパイプ | `_is_safe_gh_api_pipeline` |

### approve-skill-bash.py の判定パス（SKILL.md PreToolUse hooks 経由）

SKILL.md の `allowed-tools` に定義された `Bash(...)` パターンとマッチング。クォート外にシェルメタ文字を含むコマンドは拒否する。詳細は `approve-skill-bash.py` を Read して確認。

### Claude Code のサフィックス

Claude Code がコマンドに自動付加するパターン:
- `2>&1` — stderr リダイレクト
- `2>&1 || echo "---EXIT: $?"` — 終了コード取得

両フックの先頭でこれを除去してから判定する。

## 診断ワークフロー

承認確認が出たコマンドの原因特定:

1. **コマンドを確認**: 問題のコマンド全文を取得（引数で渡された場合は Bash 実行せずそのまま使用）
2. **permissions.allow 確認**: `config/claude/settings.json` のパターンとマッチするか確認
3. **サフィックスの影響確認**: Claude Code 付加サフィックスで permissions.allow のマッチが壊れていないか
4. **フック1（safe-commands）確認**: `approve-safe-commands.py` を Read し、末尾の if 条件でどの判定パスにも該当しないか特定
5. **フック2（skill）確認**: SKILL.md にパターンがあるか、メタ文字で弾かれていないか
6. **判定**: 自動承認が安全に実現可能なら最小限の修正箇所を決定。プロセス置換 `<(...)` やクォート外のコマンド置換 `$(...)` 等、任意コマンド実行が可能な構文を含む場合は手動承認を推奨

## 修正ワークフロー

1. **ソースファイルを編集**: `config/claude/hooks/` 内のファイルを修正
2. **テスト追加**: 修正対象のテストファイルに承認/拒否の両ケースを追加
3. **回帰テスト確認**: 既存のセキュリティテスト（攻撃パターンの拒否）が引き続きパスすることを確認
4. **テスト実行**:
   ```bash
   python3 -m unittest config/claude/hooks/test_approve_safe_commands -v
   ```
5. **ユーザーに rebuild 依頼**: Nix 管理のため `sudo darwin-rebuild switch --flake ...` が必要。詳細は nix-file-edit スキル参照

## セキュリティ原則

- **ホワイトリスト方式**: 安全と証明できるパターンのみ承認。不明なら拒否
- **正規表現はアンカー付き**: `^` と `$` で完全一致。部分一致は禁止
- **メタ文字は信頼しない**: クォート外のシェルメタ文字を含むコマンドは原則拒否
- **`_RO_CMDS` は副作用なしのコマンドのみ**: `cat`/`head`/`tail` は Read deny 迂回防止のため除外
- **テストは攻撃ベクタを含む**: 正常系だけでなくインジェクション攻撃のテストを必ず追加
- **自己無効化の禁止**: フック自体の安全性を損なう変更（全許可パターンの追加、メタ文字チェックのバイパス等）は行わない

## 修正パターン集

### パターン選択基準

最もシンプルな修正を優先する:

1. **Pattern E（permissions.allow）を最優先**: 単純な読み取り専用コマンド（パイプ・複合チェーン不要）は permissions.allow への追加で済む。既存の git サブコマンド（show, log, status 等）と同列に扱えるならここ。パイプラインでも使われるようになったら Pattern B/D に昇格する
2. **Pattern B/D（hook 側）**: パイプライン（`cmd | filter`）や複合チェーン（`cmd1 && cmd2`）での使用が想定される場合、または引数パターンの細かい制御が必要な場合
3. **Pattern C（skill）**: スキル実行中のみ必要なコマンド
4. **Pattern A**: Claude Code のサフィックス形式が変わった場合

### パターン A: 新しいサフィックス形式への対応

2ファイルでサフィックス除去の正規表現が異なる点に注意:
- `approve-safe-commands.py`: パイプ前・行継続前・末尾のいずれの位置でも除去
- `approve-skill-bash.py`: 末尾のみ除去

編集前にソース冒頭の `re.sub` を Read して現在の実装を確認すること。

### パターン B: 新しい安全コマンドの追加（safe-commands 側）

専用の正規表現パターンを作成し、`_RO_CMDS` には追加しない。ソース末尾の if 条件に追加する。既存の判定関数・パターンに倣うこと（`approve-safe-commands.py` を Read して確認）。

### パターン C: 新しいスキルコマンドの自動承認

SKILL.md の `allowed-tools` に `Bash(...)` パターンを追加し、`hooks` フロントマターに `approve-skill-bash.py <SKILL.md-path>` を登録。

### パターン D: 新しい読み取り専用複合パターンの追加（safe-commands 側）

`PATTERNS` リストに新しい `re.compile(...)` を追加。既存の `PATTERNS` を Read して書式・フラグの使い分け（`re.DOTALL` / `re.ASCII`）を確認すること。

### パターン E: permissions.allow への追加

副作用のない単純なコマンドは `permissions.allow` への追加が最もシンプル:

```json
"Bash(npm ls)",
"Bash(npm ls *)"
```

- `Bash(cmd)` と `Bash(cmd *)` のペアで追加する（Claude Code のサフィックス `2>&1` 等に対応）
- エントリはセクション内でアルファベット順にソートする（既存の空行区切りセクションを維持）
- サブコマンドで副作用があるもの（例: `npm audit fix`）は `*` でマッチするため、hook 側で正規表現制御する（パターン B 参照）

## トラブルシューティング

| 問題 | 対処 |
|------|------|
| テストが import エラー | `python3 -m unittest` でプロジェクトルートから実行 |
| 修正後も承認確認が出る | rebuild していない。Nix 管理のためシンボリックリンク先は古いまま |
| `_RO_CMDS` に追加したい | 副作用の有無を確認。引数次第で副作用があるコマンドは専用パターンで |
| 正規表現が複雑すぎる | 既存パターンを参考に。`[ \t]`（水平空白のみ）と `\s`（改行含む）の使い分けに注意 |
| プロセス置換 `<(...)` を含む | 任意コマンド実行が可能なため自動承認不可。手動承認を推奨 |
