🛡️

Claude Codeの禁止事項を番号で管理する — ルールIDシステム入門

に公開

Claude Codeの禁止事項を番号で管理する — ルールIDシステム入門

「禁止」は消える

Claude Codeに「git pushはしないで」と伝えた。しばらくは守ってくれた。しかし3日後、別のタスクの流れで実行されてしまった。

指示の「禁止」は、セッションをまたぐと薄れる。CLAUDE.mdに書いておいても、長いセッションの中で埋もれる。

この問題には2つのアプローチがある:

  1. 機械的に強制する:PreToolUseフックで物理的にブロックする
  2. 番号で管理する:ルールにIDをつけて例外処理を簡潔にする

どちらか一方ではなく、組み合わせることで「重要なルールは絶対に守られ、軽微なルールは柔軟に例外を認められる」状態になる。


アプローチ1:PreToolUseフックで機械的にブロック

最も重要な禁止事項(「絶対にやってはいけない操作」)は、フックで機械的にブロックする。Claudeの指示理解に依存しない。

フックの仕組み

実装例

// ~/.claude/hooks/safety-guard.js
const chunks = [];
process.stdin.on('data', d => chunks.push(d));
process.stdin.on('end', () => {
  const input = JSON.parse(Buffer.concat(chunks).toString() || '{}');
  const command = input?.tool_input?.command || '';

  const BLOCKED = [
    { pattern: /\bgit\s+push\b/, reason: 'ポリシー違反: git pushはローカル専用' },
    { pattern: /rm\s+-rf\s+\/(etc|usr|var|bin)/, reason: 'システムパス削除禁止' },
    { pattern: /curl[^|]*\|\s*bash/, reason: '未検証スクリプト実行禁止' },
  ];

  for (const { pattern, reason } of BLOCKED) {
    if (pattern.test(command)) {
      process.stdout.write(JSON.stringify({ decision: 'block', reason }));
      return;
    }
  }

  process.stdout.write(JSON.stringify({ decision: 'allow' }));
});
// settings.json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "node %USERPROFILE%\\.claude\\hooks\\safety-guard.js"
      }]
    }]
  }
}

フックでブロックされるルールは「例外なし」のもの。ここに追加するのは慎重にする。


アプローチ2:ルールに番号をつけて例外処理を簡潔に

フックで強制できないルール(コードスタイル・設計方針・作業フロー)は、CLAUDE.mdやrules/に書く。このとき、ルールに番号をつけると例外処理が格段に楽になる。

番号なしのルール管理(Before)

# CLAUDE.md

- テストは必ずtestcontainersを使う。モックDBは使わない
- APIキーはos.environ.getに依存せず、.envを直読みしてapi_key=に渡す
- 外部API呼び出しはtry/exceptで囲んでログを残す

例外を認めるとき:

ユーザー: 「さっきのDBの件だけど、今回のユニットテストはインメモリSQLiteでいい」
Claude: (どのルールへの例外か不明確。テストの方針なのか、DBの使い方なのか)

番号ありのルール管理(After)

# CLAUDE.md

## 開発方針(番号で参照可能)
- T01: テストは必ずtestcontainersを使う。モックDBは使わない
- T02: APIキーは.envを直読みしてapi_key=に明示的に渡す
- T03: 外部API呼び出しはtry/exceptで囲んでログを残す

例外を認めるとき:

ユーザー: 「T01を今回のユニットテストだけ例外にして。インメモリSQLiteでいい」
Claude: (T01が何かを正確に把握。このテストに限ってtestcontainersを使わなくていいと理解)

番号管理の効果

ケース 番号なし 番号あり
ルールの参照 「さっき言ったDB関係のルール」 「T01」
例外の許可 「今回はあのルールは除外して」 「T01を今回だけ例外として」
例外の範囲 曖昧(全体?今回だけ?) 明確(指定したルールのみ)
ルールの変更 「前に言ったことを変えて」 「T02を更新して」

特に例外処理が変わる。番号がないと「どのルールに対する例外か」をClaudeが文脈から推測しなければならない。番号があれば一対一に対応する。


番号の設計方針

ルールの性質でプレフィックスを分けると管理しやすい。

プレフィックス 対象
T テスト方針 T01: testcontainers使用
G Git操作 G01: featureブランチからPR
A API・外部連携 A01: .envを直読み
C コードスタイル C01: 型ヒント必須

プレフィックスは自分のプロジェクトに合わせて決める。重要なのは「同じ文字+番号で一意に参照できること」だ。


どちらをどちらに使うか

ルールの性質 適切な手段
「絶対に実行してはいけない操作」 PreToolUseフックで機械的ブロック
「方針・スタイル(例外あり)」 CLAUDE.mdに番号付きで記載
「プロジェクト固有の制約」 プロジェクトCLAUDE.mdに番号付きで記載

フックは「例外なし」のルールだけに使う。例外を認める可能性があるルールをフックに入れると、フックを無効化しないと作業できなくなる。


まとめ

  • PreToolUseフックは「絶対禁止」操作の機械的強制に使う
  • CLAUDE.mdのルールには番号をつけると例外処理が一言で済む
  • 番号はプレフィックスで性質を分類すると参照しやすい
  • フック(機械的強制)と番号管理(柔軟な例外)は補完関係

この仕組みを5層ハーネス全体に組み込む設計は有料Book「Claude Code ハーネスエンジニアリング 実践Playbook」で解説している。

Claude Code ハーネスエンジニアリング 実践Playbook(Zenn)


感想や「このケースはどうする?」みたいな質問は、コメントに気軽に書いてもらえると嬉しいです。いいねも励みになります。

筆者コメント

Discussion