1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeのパーミッション設定、231件に膨れ上がったので自動レビューする仕組みを作った

1
Posted at

対象

  • Claude Codeを日常的に使っている人
  • パーミッション確認ダイアログに疲れている人
  • HookやSkillの実用例を知りたい人

1. 「Always allow」を押し続けた末路

Claude Codeを使い込んでいると、頻繁にこんなダイアログが出てきます。

「このコマンドを実行してもよいですか?」

最初は丁寧に確認していたのですが、慣れてくるとつい「Always allow」を連打しがちになります。その結果、気づいたら settings.local.json がこんな状態になっていました。

{
  "permissions": {
    "allow": [
      "Bash(done)",
      "Bash(then mv \"$file\" \"99_archive/\")",
      "Bash(\"/Users/xxx/project/xxx/00_inbox/xxx.md\" )",
      // ... 228件続く
    ]
  }
}

231件。しかも中身を見ると Bash(done)Bash(fi) といったシェルスクリプトの制御構文の断片が永続許可として登録されていました。

「さすがにこれはまずい」と感じ、自動で整理・レビューする仕組みを作りました。

2. 何が問題か

  1. プロジェクトローカルにしか効かないsettings.local.json はそのリポジトリ専用です。別のプロジェクトで同じ git add を実行してもまた確認ダイアログが出ます。
  2. ゴミルールが大量蓄積Bash(done)Bash(fi) のようなシェルスクリプトの断片、特定ファイルパスのワンタイム的な操作が永続許可として残り続けます。
  3. 本来グローバルで許可すべきものが埋もれるBash(git add:*)Read のような汎用操作も個別のコマンド文字列として記録されてしまい、グローバル設定に昇格されることなく眠り続けます。

3. Claude Codeのパーミッションシステムを理解する

パーミッションファイルには3層構造があります。

ファイル スコープ 役割
~/.claude/settings.json グローバル(全プロジェクト共通) allow / hooks / env など
~/.claude/setting.json グローバル(deny専用) 絶対に許可しないルール
<project>/.claude/settings.local.json プロジェクトローカル gitignore対象のallow蓄積場所

ルールの書き方はツール名とオプションの組み合わせです。

{
  "permissions": {
    "allow": [
      "Write",                        // ツール全体を許可
      "Bash(git add:*)",              // Bashの特定コマンドをワイルドカード許可
      "WebFetch(domain:qiita.com)",   // 特定ドメインのみ許可
      "Skill(obsidian-workflow-manager)" // 特定スキルのみ許可
    ],
    "deny": [
      "Bash(git push:*)",
      "Bash(rm:*)"
    ]
  }
}

denyはallowより優先される点が重要です。グローバルでallowしていても、denyに記載があれば必ずブロックされます。

4. 作ったもの:全体アーキテクチャ

[ツール使用]
    │
    ▼ (PreToolUse hook)
permission-logger.sh
    │
    ▼
~/.claude/logs/permissions/YYYY-MM-DD.jsonl  (蓄積)
    │
    ├──── [Stop hook] ──► permission-review-check.sh
    │                          │
    │                          ▼ (20件以上で通知)
    │                     「📋 パーミッションログがN件溜まっています」
    │                          │
    │                          ▼ (CLAUDE.mdの指示で自動実行)
    │
    ▼ (/permission-review)
SKILL.md の7ステップ処理
    │
    ▼
settings.json / settings.local.json を更新
    │
    ▼
.last_review_ts マーカー更新 (次回カウントのリセット)
コンポーネント 役割 実行タイミング
permission-logger.sh 全ツール呼び出しをJSONLに記録 PreToolUse(自動)
permission-review-check.sh ログ件数をカウントしリマインド Stop(自動)
permission-review スキル ログ分析→整理提案→適用 /permission-review(手動 or 自動)

5. セットアップ手順

GitHubリポジトリにスクリプト・スキル一式を置いています。

https://github.com/daikin555/permission-review

前提条件として jq コマンドが必要です。

brew install jq

ステップ1 スクリプトの配置

permission-logger.shpermission-review-check.sh~/.claude/scripts/ に配置して実行権限を付与します。

mkdir -p ~/.claude/scripts
cp permission-logger.sh ~/.claude/scripts/
cp permission-review-check.sh ~/.claude/scripts/
chmod +x ~/.claude/scripts/*.sh

ステップ2 スキルの配置

SKILL.md~/.claude/skills/permission-review/ に配置します。グローバルに置くことで、どのリポジトリからでも /permission-review として呼び出せます。

mkdir -p ~/.claude/skills/permission-review
cp SKILL.md ~/.claude/skills/permission-review/

ステップ3 フックの登録

~/.claude/settings.json にPreToolUseとStopのフックを追加します。

{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/permission-logger.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/permission-review-check.sh"
          }
        ]
      }
    ]
  }
}

さらに全自動にしたい場合は、CLAUDE.md に以下の一文を追加します。

## Permission Review

Stopフックから「📋 パーミッションログが〇件溜まっています」というメッセージが届いたら、
自動的に `/permission-review` スキルを実行する

これだけで、セッション終了のたびにClaudeが自分でパーミッションを整理してくれるようになります。

6. 仕組みの詳細

permission-logger.sh(ログ収集)

PreToolUseフックで呼び出され、全ツール呼び出しをJSONL形式で記録します。

#!/bin/bash
LOG_DIR="$HOME/.claude/logs/permissions"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d).jsonl"

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')

SUMMARY=$(echo "$INPUT" | jq -c '.tool_input // {} | {
  command: .command,
  file_path: .file_path,
  url: .url,
  skill: .skill
} | with_entries(select(.value != null))' 2>/dev/null || echo "{}")

echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"tool\":\"$TOOL_NAME\",\"summary\":$SUMMARY}" >> "$LOG_FILE"

exit 0

設計のポイントは2点あります。

  • 必ず exit 0 で終了する — ブロックせずツール実行を継続させるため
  • 重要フィールドだけ保存する — ファイルサイズの肥大化を防ぐため

記録されるログはこのようになります。

{"ts":"2026-02-18T20:59:07Z","tool":"Bash","summary":{"command":"git add 02_atomic/foo.md"}}
{"ts":"2026-02-18T20:59:09Z","tool":"Read","summary":{"file_path":"/Users/itoudaichi/.claude/settings.json"}}
{"ts":"2026-02-18T20:59:13Z","tool":"Skill","summary":{"skill":"obsidian-workflow-manager"}}

パターン正規化(スキルの核心)

スキルの中核処理は、実際のコマンド文字列をパーミッションルール形式に変換するパターン正規化です。

実際のコマンド 正規化後のパターン
git add 02_xxx/foo.md Bash(git add:*)
git commit -m "fix: ..." Bash(git commit:*)
python3 << 'EOF' ... Bash(python3:*)
https://qiita.com/article/123 WebFetch(domain:qiita.com)
Read Read(そのまま)
skill名あり Skill(スキル名)

コマンドの先頭語を抽出して Bash(コマンド:*) 形式にまとめることで、個別のファイルパスや引数を気にせず汎用的なルールを生成できます。

7. 実際に動かした結果

実際に1日分のログを分析した結果を示します。

分析済みログ 1ファイル(2026-02-19.jsonl)/ 総ツール呼び出し数 165回

グローバルへの追加推奨として以下が提案されました。

パターン 回数
Read 約50回
Edit 約18回
Bash(python3:*) 約10回
Bash(grep:*) 約8回
Bash(ls:*) 約6回
Bash(find:*) 約5回

ローカルから削除候補として40件以上のシェル制御構文の断片が挙がりました。

Bash(done)
Bash(then)
Bash(else)
Bash(fi)
Bash(do if [ -f "$file" ])
...(40件以上)

適用後の結果です。

グローバルへ追加: 12件
  + Read, Edit, Write, Glob
  + Bash(ls:*), Bash(grep:*), Bash(echo:*)
  + Bash(find:*), Bash(mkdir:*), Bash(head:*)
  + Bash(wc:*), Bash(chmod:*)

ローカルから削除: 48件(シェル断片 + グローバル重複)
ローカル残存ルール: 182件(プロジェクト固有のルールが残る)

231件 → 182件(ローカル)+ 12件(グローバル)という形でスッキリしました。

8. 設計で学んだこと

PreToolUseはPermission判定の後に発火する

フックの発火タイミングを最初勘違いしていました。実際のフローはこうなっています。

Claudeがツールを呼び出そうとする
    ↓
Permissionチェック(settings.json / settings.local.json を参照)
    ↓ allowの場合のみ
PreToolUse Hook 発火  ← ログを取れるのはここ
    ↓
ツール実行
    ↓
PostToolUse Hook 発火

「拒否」されたログは現在のClaude Codeでは取得できません。ただし、hookに届いたものはすべて許可された操作という保証でもあります。今回の目的は「許可した操作の整理」なので、実用上はまったく問題ありません。

exit codeの意味

exit code 効果
0 通常実行を継続(stdoutはClaudeへのフィードバック)
2 ツール実行をブロック(stderrが表示される)
その他 hook自体の失敗として扱われる

ログ収集用のhookは必ず exit 0 で終わらせてください。誤って exit 2 にするとあらゆるツール実行がブロックされます。

グローバル/ローカルの使い分け原則

配置先 何を置くか
グローバル (settings.json) 全プロジェクト共通の汎用操作 Read, Edit, Bash(ls:*)
ローカル (settings.local.json) プロジェクト固有の操作 Skill(obsidian-workflow-manager)
グローバルdeny (setting.json) 絶対に禁止する操作 Bash(git push:*), Bash(rm:*)

スキルはグローバルに配置する

最初はスキルをプロジェクト内の .claude/skills/ に置いていました。この場合、そのリポジトリ内でしか使えません。~/.claude/skills/ に置くことで、どのリポジトリからでも /permission-review として呼び出せるようになります。スキルは基本的にグローバルに置くのが正解です。

9. まとめ

  • PreToolUse hookで全ツール呼び出しをログ収集 — JSONLで日付別に蓄積
  • Stopフックで自動リマインド — セッション終了時に件数を通知
  • スキルがログを分析して整理を提案 — グローバル移行 / ゴミ削除を半自動化
  • 結果として231件 → 182件(ローカル)+ 12件(グローバル)でスッキリした

GitHubリポジトリ: https://github.com/daikin555/permission-review

今後の改善余地

  • 複数プロジェクトのローカル設定を横断的に比較・統合する機能
  • ログローテーション(30日以上前のファイルを自動削除)
  • Stopフックの通知しきい値をプロジェクト単位で設定できるようにする
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?