CLAUDE.md の禁止事項を LLM 判断ではなくシェルレベルで強制するためのフック設定。 permissions.deny(静的パターン)と PreToolUse フック(動的検査)の二段構え。

なぜ Hook 化するのか

CLAUDE.md は system-reminder で「関連性なければ無視せよ」と明示注入されるため、**強制力のない助言(advisory)**に過ぎない。 不可逆な被害を伴う禁止(main 直接 push、機密ファイル混入、本番デプロイ事故)は、シェルレベルで止めない限り事故を完全には防げない。

  • CLAUDE.md = behavioral guidance(LLM が判断、確率的)
  • Hooks / permissions.deny = technical enforcement(ハーネスが判断、決定論的)

構成

ファイル役割
scripts/hooks/pre_bash_guard.shPreToolUse Bash フック本体。動的パターン検査
scripts/hooks/post_edit_guard.shPostToolUse Edit/Write フック本体。コード違反検知
scripts/hooks/instructions_loaded_log.shInstructionsLoaded フック本体。CLAUDE.md / rules のロード記録(下記参照)
scripts/hooks/session_start_prompts.shSessionStart フック本体。運用プロンプト受信箱の自動注入(下記参照)
scripts/hooks/context_budget_warn.shStop フック本体。context 肥大時に handover / 新セッションを促す警告(下記参照)
scripts/hooks/settings.example.json.claude/settings.json の雛形(permissions.deny + PreToolUse / PostToolUse / InstructionsLoaded / SessionStart / Stop 配線)
.claude/settings.json各ワークスペースの実設定(gitignore・各自で配置)
scripts/pre-push-check.shgit pre-push hook(既存・本フックと相補)

初回セットアップ(各ワークスペースで実施)

  1. テンプレートを .claude/settings.json にコピー(.claude/ は gitignore)

    mkdir -p .claude
    cp scripts/hooks/settings.example.json .claude/settings.json
    
  2. 実行権限を確認(git clone 直後に落ちることがある)

    chmod +x scripts/hooks/pre_bash_guard.sh
    
  3. Claude Code を再起動して設定読み込みを反映

  4. 動作確認: 安全なコマンドを試す → 通る、危険コマンドを試す → 拒否される

    ユーザ: 「git push origin main を実行して」
    → Claude が Bash 呼出 → Hook が exit 2 → ❌ Blocked: ... が表示される
    

何がブロックされるか

permissions.deny(静的・即時拒否)

パターン理由
Bash(git push origin main*)main への直接 push 禁止(PR 経由のみ)
Bash(git push origin master*)(リネーム未対応リポ向けの保険)
Edit/Write(./.clasprc.json)clasp 認証トークン
Edit/Write(./creds.json)GCP サービスアカウント
Edit/Write(./.clasp.json)clasp 作業コピー(dev/prod 切替で生成)
Edit/Write(./.env)環境変数(API キー等)

PreToolUse Bash フック(動的・スクリプト判定)

パターン検知例例外
rm -rfrm -rf ./importantnode_modules / dist / build / coverage / .next / .cache / tmp は許可
git reset --hardgit reset --hard HEAD~3なし(巻戻しは手動で)
git push --force / -f / --force-with-leasegit push -f origin featureなし
clasp push 直接呼出clasp push -fnpm run push:dev 経由を強制
npm run push:prod / deploy:prod同左なし(CI 専属、手動は禁止)
機密ファイルの git add/commitgit add .clasprc.json / git commit -m wip .env.clasp.dev.json .clasp.prod.json は committed テンプレートなので除外
.claude/settings.json のシェル経由書換echo {} > .claude/settings.jsonEdit ツール経由は許可(明示的承認のため)

PostToolUse Edit/Write/MultiEdit フック(コード書込み後検知)

Edit / Write / MultiEdit の直後に走り、Claude が新たに書込んだコード片にコーディング規約違反があれば exit 2 で通知する。 既存ファイルに残存する違反は誤検知しない — 旧コンテンツ (Edit の old_string / Write の git HEAD 内容) と新コンテンツの出現回数を比較し、新規が増えた場合のみ通知する。

パターン検知例例外
PropertiesService.getScriptProperties() 直呼びvar p = PropertiesService.getScriptProperties();mas/000_infra/001_env.js(Env モジュール本体)
prod scriptId のハードコードvar s = '12X9...';.clasp.prod.json の値と一致).clasp.*.json テンプレ自身、001_env.js
CLAUDE.md / CLAUDE.local.md の @ import@docs/_internal/foo.md / @CLAUDE.local.md / @./README.md[email protected] 等は誤検知しない(行頭 or 空白後の @docs/@CLAUDE.local.md@./ のみ対象)

スキャン対象:

  • ソースコード(.js / .gs / .ts / .tsx / .html): PropertiesService + scriptId 検査
  • CLAUDE.md / CLAUDE.local.md(任意のサブディレクトリ含む): @ import 検査
  • .md(上記以外)/ .json などは対象外

@ import 検知ロジックの根拠: humanlayer の CLAUDE.md 指針「@ import は起動時に全展開され context を圧迫する」を技術的に強制。 PR #511 で導入後、PR #514 で @ を 19 箇所削除して context を 495,492 → 0 chars に削減した経緯を踏まえ、 新規混入を post_edit_guard.sh で阻止 (PR #515)。Progressive Disclosure (CLAUDE.md は索引、docs/ は図書館) の運用に統一。

注: 設定変更後に許可/拒否を変えたい場合は scripts/hooks/pre_bash_guard.sh または scripts/hooks/post_edit_guard.sh.claude/settings.jsonpermissions.deny を編集する。

既存違反 (テクニカルデット) — クリーンアップ完了

2026-05-07 時点の PropertiesService.getScriptProperties() 直呼び 4 件は全て Env モジュール経由に置換済み:

旧違反箇所Env API 置換
mas/100_config/101_sys_config.js:1074, 1705(schema hash)Env.lastSchemaHash() / Env.setLastSchemaHash()
mas/800_ops/803_sync_tool.js:55(prod→dev 同期)Env.spreadsheetId() (既存) + Env.prodSpreadsheetId() (新設)
mas/800_ops/811_audit_checker.js:42, 126(N-30 監査)Env.n30LastCheckedAt() / Env.setN30LastCheckedAt()

以後は PostToolUse フックが新規違反の混入を技術的に阻止する。新たに PropertiesService の直呼びが必要なケースが出てきたら、Env モジュール (mas/000_infra/001_env.js) に専用のラッパー関数を追加する運用を維持する。

トラブルシュート

Hook が動かない

  • .claude/settings.json が存在し JSON valid か確認: node -e "JSON.parse(require('fs').readFileSync('.claude/settings.json','utf8'))"
  • スクリプトが実行可能か確認: ls -l scripts/hooks/pre_bash_guard.sh-rwxr-xr-x 期待)
  • Claude Code 再起動を実施したか
  • $CLAUDE_PROJECT_DIR が解決されているか(設定ファイル内のパスが絶対参照になっているか)

誤検知が出る

  • pre_bash_guard.sh 末尾の各 if echo "$COMMAND" | grep -qE '...'; then block ... ブロックを必要に応じて緩める
  • ホワイトリスト的にコマンド先頭一致で許可したい場合は block 直前に early-return を追加

本当に必要な破壊的操作を実行したい

  • フックを一時的に無効化するのではなく、Claude Code の外(手動ターミナル)で実行する運用が安全
  • 例: git reset --hard は手動 git 操作で。Claude には完了後の git 状態だけ伝える

更新ルール

  • フック追加時: pre_bash_guard.sh または post_edit_guard.sh に検知ブロックを 1 件追加 → このドキュメントの表に行追加

  • 静的パターン追加時: settings.example.jsonpermissions.deny に追記 → 既存ワークスペースは手動で .claude/settings.json を同期

  • スクリプトのテストはローカルで以下のように実行可能:

    # PreToolUse (Bash)
    echo '{"tool_input":{"command":"git push origin main"}}' | bash scripts/hooks/pre_bash_guard.sh
    echo $?  # 期待: 2
    
    # PostToolUse (Edit)
    echo '{"tool_name":"Edit","tool_input":{"file_path":"/tmp/x.js","old_string":"","new_string":"PropertiesService.getScriptProperties()"}}' \
      | bash scripts/hooks/post_edit_guard.sh
    echo $?  # 期待: 2
    

外部 API 書込の確認ゲート (COM-0381)

pre_bash_guard.sh は外部 API 書込 (curl/wget-X POST/PUT/PATCH/DELETE--request/--method--upload-file) を 検知すると、ハードブロック (exit 2) でなく permissionDecision=ask をハーネスへ返し、操作単位のユーザー確認を要求する。 包括的「yes」を外部書込の承認とみなす拡大解釈 (ADR-0115 §1.2 の実害③) を技術的に遮断する。Claude は token 等で自己バイパスできない。

  • 除外: gh (GitHub・別認証/監査)・localhost/127.0.0.1 (ローカル dev)。-X(大文字) で proxy -x と区別。
  • 動作確認 (payload 例):
    # 外部書込 → ask (permissionDecision JSON を出力)
    printf '{"tool_input":{"command":"curl -X POST https://saas.example.com/api -d x=1"}}' \
      | bash scripts/hooks/pre_bash_guard.sh   # → {"hookSpecificOutput":{...,"permissionDecision":"ask",...}}
    # GET / localhost / gh は素通り (出力なし・exit 0)
    
  • jq 不在環境では ask JSON を組めないため安全側でハードブロック (exit 2) に倒す。

InstructionsLoaded ロードログ (ADR-0129 KPI-7)

InstructionsLoaded フック (scripts/hooks/instructions_loaded_log.sh) が、CLAUDE.md / .claude/rules/*.md / nested CLAUDE.md のロードを .claude/logs/instructions_loaded.log (gitignored) に 1 行 JSON で記録する (file_path + load_reason)。これで path-scoped ロードの無音失効を観測できる。

  • CI 静的 lint (PR ゲート): node scripts/adr0129-rules-loading-lint.mjs — gas.md の paths: 健全性 (ベース dir 実在・GAS レイヤーのカバー漏れ) と InstructionsLoaded フック配線を検証。実 claude は回さない。
  • ローカル/定期チェック: node scripts/adr0129-loadlog-check.mjs — ログに gas.md の load_reason=path_glob_match 実績があるか確認 (exit 0=healthy / 1=未確認 / 2=ログ未採取)。0 件なら GAS ファイルを 1 つ Read するセッションを 流して再実行 → それでも 0 件なら失効を疑い tasks/adr0129/kpi6_paths_verification_log_2026-06-09.md の決定的再検証へ。
  • ロード判定は本フックログが唯一の権威。debug ログ grep や un-primed self-report は偽陰性 (memory: claude-rules-loading-verification)。

SessionStart 受信箱注入(運用プロンプトの自動表示)

SessionStart フック (scripts/hooks/session_start_prompts.sh) が、セッション開始時に tasks/prompts/ 直下の自分の役割宛てプロンプト(新しい順 6 本+総数。見出しは定型前置きを除いて 60 字まで)を <tasks-prompts-inbox> としてセッション文脈へ自動注入する。「handover を読んで」と言わなくても着信に気づける。本数・見出し長はセッション開始時の context 消費を抑えるため絞っている(2026-06-12 に 12 本×80 字 → 6 本×60 字へ変更)。

  • 役割判定の規則: クローン名で自動判定する。-doc / -sub サフィックスと web パスは sub、それ以外は main。sub には *_to_sub / sub_* / handover_* だけが表示される。

  • matcher: startup|resume|clear|compact。再開 (resume) でも発火する(当初 startup|clear|compact で再開時に出ない取りこぼしがあり修正済み)。

  • 着信通知 Actions と対: .github/workflows/ops-prompt-notify.yml が main への新着プロンプトを GitHub issue(label: ops-prompt)で通知し、tasks/prompts/archive/ への退避(= 消費・ADR-0134)を検知して自動 close する。受信箱注入が「読む側」、通知 Actions が「届いたことを知らせる側」。

  • テンプレ同期手順は従来どおり: 初回セットアップの手順 1(settings.example.json をコピー)に含まれる。既存ワークスペースは SessionStart ブロックを .claude/settings.json へ手動同期する。

  • 動作確認(matcher と無関係に中身を確認できる):

    CLAUDE_PROJECT_DIR=$(git rev-parse --show-toplevel) bash scripts/hooks/session_start_prompts.sh
    # → role="sub" のクローンでは sub 宛てだけが出る
    

Stop context 警告(malformed 連鎖の予防)

Stop フック (scripts/hooks/context_budget_warn.sh) が、各ターン終了時に現在の context トークン量を測り、危険帯に入ったら handover / 新セッション再開を促す警告(systemMessage)を出す。

  • なぜ: ツール呼び出しの malformed("Your tool call was malformed and could not be parsed")は context サイズに強く相関する。実測(全 transcript 横断)で 34 件 / 12 セッション・cache_read ≥50k 帯に集中し、1 セッションに 13 件・6 件の連鎖が起きる。根本原因は上流(モデルのツール呼び出しシリアライズ)で塞げないが、context を危険帯に入れない運用で「連鎖による高コスト」をほぼ消せる。本フックは人の記憶に頼らず自動で促す歯止め。

  • しきい値: WARN_AT=150000 トークンで初回警告、以降 STEP=50000 刻みで 1 回ずつ。session_id ごとに /tmp マーカーで抑制し、compact 等で context が下がったらマーカーをリセットして再警告できる。

  • context の算出: transcript_path 末尾 200 行から最新 assistant の usagecache_read + cache_creation + input)を読む(全文走査を避け軽量化)。jq が無ければ無動作。

  • 安全性: 常に exit 0。stop は決してブロックしない(無限ループ防止)。閾値未満や transcript 不在では無出力。

  • テンプレ同期: 既存ワークスペースは Stop ブロックを .claude/settings.json へ手動同期する(settings.example.json 参照)。

  • 動作確認:

    # 160k 相当の擬似 transcript を渡して警告が出るか
    T=$(mktemp -d); printf '{"type":"assistant","message":{"usage":{"cache_read_input_tokens":160000,"cache_creation_input_tokens":0,"input_tokens":100}}}\n' > "$T/tr.jsonl"
    printf '{"session_id":"manualtest","transcript_path":"%s/tr.jsonl"}' "$T" | bash scripts/hooks/context_budget_warn.sh
    # → {"systemMessage":"⚠️ context 約 160k …"} が出れば OK
    
  • 関連: auto-memory long-session-toolcall-glitch(2 型の切り分け・母集団データ・effort 特徴)。

関連ドキュメント

  • docs/_internal/git_workflow.md — Git 運用全般
  • docs/dev/dev_mas-096_clasp_push_protection.md.claspignore 保護(git/clasp 層)
  • docs/dev/dev_mas-195_pre_push_hook.md — git pre-push 自動チェック(git 層)
  • scripts/pre-push-check.sh — 上記の実体