0
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.mdに「やらないこと」を書く 顧客案件でAIに守らせるルールの決め方

0
Posted at

はじめに

顧客案件でClaude Codeに作業を任せるとき、「こうしてほしい」は書いても、「これはしないでほしい」は書き忘れがちです。そして、書いたつもりでも、守られたかどうかを確かめる手段がないと、次の案件で同じ場所が抜けます。

この記事では、CLAUDE.md(Claude Codeがセッション開始時に読み込む、プロジェクト用の指示ファイル)に「やらないこと」を書くときの考え方と、書くだけでは足りないものを hook と事後検査に分ける方法をまとめます。例は、この記事のために作った架空のプロジェクトのものです。実際の案件のルールは載せていません。

環境

  • macOS 26.6.2、Node.js v22.22.3、git 2.54.0、jq
  • Claude Code 2.1.283(2026-09-27に確認)

hook や CLAUDE.md の挙動は、バージョンによって変わる可能性があります。

この記事をざっくり図解

[CLAUDE.md]  方針と理由を伝える。守るかどうかはAIの判断に任せる
     ↓ 取り返しがつかないものだけ
[PreToolUse hook]  コマンドを実行する前に機械的に止める
     ↓ 実行後に確かめたいものだけ
[事後検査(grep / pre-commit / CI)]  差分に禁止事項が入っていないかを調べる

1 CLAUDE.mdに書くもの

私は、書く内容を3つに分けています。「やること(規約)」「やらないこと」「確認してから進めること」です。次が架空の例です。

# CLAUDE.md(架空の受発注管理システム用の例)

## このプロジェクトについて
- 受発注データを管理する社内向けWebアプリ。Node.js 22 / PostgreSQL
- テストは `npm test`、Lintは `npm run lint`

## やること(規約)
- ブランチ名は `feature/{チケット番号}-{短い説明}`
- コミットメッセージは Conventional Commits。AIの署名や生成元の記載は入れない

## やらないこと
- main に直接 push しない。force push もしない
  - 理由: 履歴を戻せなくなる。代わりに: ブランチを切って PR にする
- `--no-verify` でコミット時の検査を飛ばさない
  - 理由: 検査の意味がなくなる。代わりに: 落ちた原因を直す
- 顧客名、担当者名、契約金額を、コード・コメント・コミットメッセージに書かない
  - 理由: リポジトリは委託先とも共有される。代わりに: 案件コード(PJ-XXXX)を使う
- `.env` など認証情報のファイルを読まない、書かない
- 本番DBに接続しない。確認は検証環境で行う

## 確認してから進めること
- DBのマイグレーションを追加するとき
- 既存APIのレスポンス形式を変えるとき

2 「やらないこと」の書き方

書くときに気をつけているのは、次の3点です。

  1. 理由と、代わりにやることをセットで書く。 「push しない」だけだと、AIは代替手段を自分で探します。「PR にする」まで書いてあれば、そちらに寄せてくれます。
  2. 数を絞る。 公式ドキュメントは、1つの CLAUDE.md を200行以下にすることを勧めていて、長くなるほど遵守率が下がるとも書いています。矛盾する指示があると、どちらかが任意に選ばれる、という記述もあります。なんでも書くと、大事な行が埋もれます。
  3. 検査できる形で書く。 「適切に対応する」ではなく、「--no-verify を使わない」のように、あとで grep で確かめられる文にします。

3 どこで守らせるか

すべてを hook にする必要はありません。書いた規則を、置き場所で分けます。

規則 置き場所 理由
main への push、force push、--no-verify hook コマンド文字列に現れ、事故ると戻せない
顧客名を書かない 事後検査 コマンドには現れず、書かれた内容に出る
認証情報の読み書き hook ツール呼び出しで判定できる(別記事)
本番DBに接続しない 権限で分ける hook より前に、そもそも認証情報を渡さない
ブランチ名、コミット形式 CLAUDE.md + CI 守られなくても直せる
「確認してから進める」 CLAUDE.md のみ 機械で判定できない

公式ドキュメントにも、CLAUDE.md は「強制される設定としては扱われず、コンテキストとして読まれる」とあり、実行を確実に止めたいなら PreToolUse hook を使うよう書かれています。

4 hookで止める

まず、止める規則を3つだけ書いた hook です。Bash の実行前に、コマンド文字列を見ます。

.claude/hooks/block-risky-bash.cjs
#!/usr/bin/env node
"use strict";

// 「やらないこと」のうち、事故ると取り返しがつかないものだけをここで止める
const RULES = [
  {
    name: "force push",
    pattern: /\bgit\s+push\b[^;&|]*\s(--force(?![\w-])|-f(?![\w-]))/,
    message: "force push は禁止です。必要なら人間に確認してください。",
  },
  {
    name: "main への直接 push",
    pattern: /\bgit\s+push\b[^;&|]*\s(origin\s+)?main(?![\w./-])/,
    message: "main への直接 push は禁止です。ブランチを切って PR にしてください。",
  },
  {
    name: "検査の回避",
    pattern: /\bgit\s+commit\b[^;&|]*\s--no-verify\b/,
    message: "--no-verify は禁止です。検査で落ちた理由を直してください。",
  },
];

async function readStdin() {
  let data = "";
  for await (const chunk of process.stdin) data += chunk;
  return data;
}

async function main() {
  let payload;
  try {
    payload = JSON.parse(await readStdin());
  } catch {
    process.exit(0); // 読めない入力は止めない(通常の許可フローに任せる)
  }

  const command = payload?.tool_input?.command;
  if (payload?.tool_name !== "Bash" || typeof command !== "string") process.exit(0);

  for (const rule of RULES) {
    if (rule.pattern.test(command)) {
      console.error(`BLOCKED (${rule.name}): ${rule.message}`);
      process.exit(2);
    }
  }
  process.exit(0);
}

main();

--force-with-lease は止めない設計にしています。main の後ろに続く文字を見ているのは、\b だけだと main-backup のようなブランチ名まで止めてしまうためです。

流し込んで確かめます。

#!/usr/bin/env bash
# 各コマンドをhookに流して、終了コードを見る
check() {
  jq -n --arg c "$1" '{tool_name:"Bash",tool_input:{command:$c}}' | node block-risky-bash.cjs 2>/dev/null
  printf 'exit=%s  %s\n' "$?" "$1"
}
check 'git push origin feature/login'
check 'git push --force origin feature/login'
check 'git push -f origin feature/login'
check 'git push --force-with-lease origin feature/login'
check 'git push origin main'
check 'git commit -m "fix" --no-verify'
check 'git push origin +feature/login'
check 'git push origin HEAD:main'
check 'sh -c "git push -f"'
check './scripts/deploy.sh'
check 'git push origin main-backup'
check 'git push origin main'
exit=0  git push origin feature/login
exit=2  git push --force origin feature/login
exit=2  git push -f origin feature/login
exit=0  git push --force-with-lease origin feature/login
exit=2  git push origin main
exit=2  git commit -m "fix" --no-verify
exit=0  git push origin +feature/login
exit=0  git push origin HEAD:main
exit=2  sh -c "git push -f"
exit=0  ./scripts/deploy.sh
exit=0  git push origin main-backup
exit=2  git push origin main

登録は settings.json です(スクリプトには chmod +x が必要です)。

.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-risky-bash.cjs" }
        ]
      }
    ]
  }
}

5 事後に検査する

「顧客名を書かない」は、コマンドには現れず差分の中身に出るので、コミット前に検査します。語のリストはリポジトリの外に置きます。リポジトリに顧客名を書かないための規則なのに、リストを入れたら本末転倒だからです。

check-banned-words.sh
#!/usr/bin/env bash
# ステージ済みの差分に、書いてはいけない語(顧客名など)が入っていないか調べる
# 語のリストはリポジトリの外に置く(リポジトリに顧客名を書かないため)
set -euo pipefail

LIST="${BANNED_WORDS_FILE:-$HOME/.config/project-banned-words.txt}"
[ -f "$LIST" ] || { echo "banned words file not found: $LIST" >&2; exit 1; }

# 追加された行だけを対象にする(+++ のファイル名行は除く)
if git diff --cached -U0 | grep -E '^\+' | grep -v '^+++' | grep -i -F -f "$LIST"; then
  echo "NG: 書いてはいけない語が含まれています。上の行を直してください。" >&2
  exit 1
fi
echo "OK"

一時リポジトリで確かめると、通常の差分は OK、禁止語を含む差分は行を表示して終了コード1になります。

OK
+// acmetrading 用の暫定対応
NG: 書いてはいけない語が含まれています。上の行を直してください。

pre-commit hook や CI に組み込めば、AI が書いた変更も人が書いた変更も、同じ基準で確認できます。

できないこと

  • CLAUDE.md は強制ではありません。 長い指示や、矛盾する指示は守られにくくなります。書いたから安心、とは考えないほうがよいです。
  • hook が見ているのは、ツール呼び出しだけです。 上のテストでも、抜け道が見つかりました。git push origin +feature/login(+ を付けた refspec は force push と同じ動きです)、git push origin HEAD:main、中で force push をするシェルスクリプト(./scripts/deploy.sh)は、いずれも exit=0 で通ります。正規表現を足せば塞げるものもありますが、コマンド文字列で判定する以上、完全にはなりません。
  • hook 自体を AI が書き換えられる状態では、意味が薄れます。 hook ファイルの編集を権限で制限するなどの併用を検討してください(この記事では検証していません)。
  • 事後検査は、追加された行の文字列一致だけです。 略称や表記ゆれは通りますし、語のリストは人が更新し続ける必要があります。
  • 実際の案件で使っているルールの一覧や検知パターンは、案件ごとに調整しているため、この記事には載せていません。

最後に

「やらないこと」は、書く場所によって守られ方が変わります。理由を添えて CLAUDE.md に書き、事故ると戻せないものは hook で止め、内容に出るものは事後に検査する。この3段に分けるだけでも、書き忘れや抜けは見つけやすくなります。気になったところから、手元の環境で試してみてください。

参考文献

  • Claude Code Docs「How Claude remembers your project」(CLAUDE.md、サイズと矛盾の注意、hook との使い分け)
  • Claude Code Docs「Hooks reference」(PreToolUse の入力JSON、exit code 2、settings.json の書式)
0
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
0
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?