【設計】Claude Code Harness 入門 — チームで共有する .claude/ の3層構成(連載 第1回)

この記事でわかること
- 個人設定では足りず、.claude/ をチーム共有するべき理由
- 「土台(settings)→ ガード(hooks)→ 拡張(agents / commands / skills)」の3層で整理する考え方
- permissions の allow / deny / ask の使い分けと、最初に貼るべき deny の定番
- 個人 override(settings.local.json)とチーム共有の境界線
Claude Code 1.0 以降 / リポジトリのルートに .claude/ を作って commit できる権限。公式ドキュメントの Settings / Hooks / Subagents / Skills を一度眺めていると読みやすいです
概要: Claude Code を導入したチームで「個人ごとの設定が散らかる」「破壊的なコマンドをうっかり実行する」「コミットに不要な署名が混入する」といった問題に当たったとき、
.claude/をチーム共有のハーネスとして整備すると一気に解決します。本記事はその全体像を、実際にあるモノレポで運用している.claude/の構造を題材に解説する連載の第1回です。
はじめに
Claude Code を一人で使っているうちは、設定ファイルは ~/.claude/settings.json で十分です。permissions を緩めて、自分の好みのフックを少しだけ足せば、それで動きます。
しかしチームでの利用が始まると、一気に話が変わります。
- 「A さんの環境ではすぐ実行できたのに、B さんは毎回 permission の確認ダイアログが出る」
- 「Claude が
terraform/prodをterraform applyしようとしてヒヤッとした」 - 「コミットメッセージに
Co-Authored-By: Claudeが紛れ込んでいて、本番ブランチに残ってしまった」 - 「
.envをうっかり Read されて、シークレットが画面に出てしまった」
これらは「Claude Code 本体の機能だけでは防ぎきれないが、.claude/ の設計次第で全部止められる」種類の問題です。
本連載では、ある AI 系プロダクト群を抱えるモノレポで実運用している .claude/ を題材に、チームで共有するハーネスをどう設計するかを 4 回に分けて整理します。
1. なぜ「個人設定」では足りないのか
Claude Code には設定の階層があります。雑にまとめると次の 3 段です。
- ユーザー設定
~/.claude/settings.json— 個人ごと、全プロジェクト共通 - プロジェクト共有設定
<repo>/.claude/settings.json— リポジトリにコミットされ、全員に効く - プロジェクトローカル設定
<repo>/.claude/settings.local.json— gitignore 対象、個人 override
問題が起きやすいのは「プロジェクト固有のルールをユーザー設定に書いてしまう」ケースです。たとえば「このリポジトリの terraform/dev と terraform/prod には絶対 apply するな」というルールを個人の設定で運用していると、次に入ったメンバーには伝わりません。
逆に、Claude Code 自身は CLAUDE.md(リポジトリに置く指示書)からそのルールを読み取って従ってくれますが、LLM 任せのガードは確率的にすり抜けることがあります。とくに長い会話や、複数 agent を経由する操作では危険です。
そのため、チーム運用ではこうなります。
- 意図 はテキスト(
CLAUDE.md)で LLM に伝える - 守らせたい不変条件 はコード(hooks / permissions)で機械的に止める
- 反復作業 は slash command / skill で型化する
これを実現する箱が .claude/ です。
2. .claude/ の 3 層構成
運用していて落ち着いた構造はこうです。
.claude/
├── settings.json # 土台:permissions・env・hooks・statusLine
├── settings.local.json.example # 個人 override のテンプレ(実体は gitignore)
├── hooks/ # ガード層:起動時 context、Pre/PostToolUse での介入
├── agents/ # 拡張層:用途特化の subagent 定義
├── commands/ # 拡張層:薄い slash command
└── skills/ # 拡張層:多段手順を文脈ごと束ねる playbookこれを「土台 → ガード → 拡張」の 3 層として捉えると、何をどこに書くかの判断が一気にラクになります。
┌─────────────────────────────────────────────────┐
│ 拡張層: agents / commands / skills │
│ 「やりたいこと」を型として提供する │
├─────────────────────────────────────────────────┤
│ ガード層: hooks │
│ 「やってはいけないこと」を機械的に止める │
├─────────────────────────────────────────────────┤
│ 土台: settings.json │
│ permissions / env / hook 配線 / status line │
└─────────────────────────────────────────────────┘大事なのは、下層ほど「守り」、上層ほど「攻め」であることです。ガード層が薄いと拡張層がいくら充実してもチームに展開できません。一方、ガードだけ充実していて拡張が無いと、Claude Code を使う旨味が出ません。
3. 土台 — settings.json で何を決めるか
まずは土台の settings.json です。実運用ではだいたいこの 4 要素を書きます。
3.1 permissions(allow / deny / ask)
一番効くのは permissions です。allow / deny / ask の 3 つに分かれていて、それぞれ役割が違います。
{
"permissions": {
"allow": [
"Bash(git:*)", "Bash(gh:*)", "Bash(make:*)",
"Bash(poetry:*)", "Bash(python:*)", "Bash(pytest:*)",
"Bash(docker compose:*)", "Bash(docker:*)",
"Bash(ls:*)", "Bash(cat:*)", "Bash(grep:*)",
"WebFetch", "WebSearch"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./**/.env)",
"Read(./**/secrets/**)",
"Bash(terraform apply:*)",
"Bash(terraform destroy:*)",
"Bash(rm -rf /*)",
"Bash(git push --force:*)"
],
"ask": [
"Bash(git push:*)",
"Bash(git reset --hard:*)",
"Bash(docker volume rm:*)"
],
"defaultMode": "default"
}
}defaultMode に "auto" を指定する例を見かけますが、プロジェクト共有の .claude/settings.json では無視されます(ユーザー設定 ~/.claude/settings.json 側でのみ有効)。リポジトリにコミットする側に書いてもチームには効かないので、ここでは既定の "default" にしてあります。ポイントは 3 つあります。
第一に、allow は「許可」ではなく「確認を省略するリスト」だと考えるのがコツです。Bash(grep:*) を allow しておくと、grep が走るたびに毎回ダイアログが出るのを抑えられます。逆に、副作用がある操作は絶対に allow に入れません。
第二に、deny は黄金パターンを最初に並べます。.env / secrets/ の Read 遮断、terraform apply|destroy の遮断、rm -rf / 系の遮断、git push --force の遮断は、ほぼどのプロジェクトでも入れて損がありません。
第三に、ask には「ほとんどの場合 OK だが、念のため確認したい」操作を入れます。git push や docker volume rm などです。allow にすると怖い、deny にすると不便、という操作の置き場所です。
3.2 env
環境変数も settings.json で持てます。シークレットは入れず、プロジェクトを識別するための定数だけ持つのが安全です。
{
"env": {
"CLAUDE_CODE_PROJECT": "my-monorepo"
}
}3.3 hooks
hooks は次節で詳しく扱いますが、設定上は「どのイベントで」「どのスクリプトを呼ぶか」を配線するだけです。
{
"hooks": {
"SessionStart": [{ "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh", "timeout": 10 }] }],
"PreToolUse": [
{ "matcher": "Read", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/secret-guard.sh", "timeout": 5 }] },
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/bash-guard.sh", "timeout": 5 }] }
],
"PostToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/strip-claude-signature.sh", "timeout": 5 }] }
]
}
}matcher で「どの tool に対する hook か」を絞れます。Read と Bash で別のスクリプトを当てているのがポイントです。
3.4 statusLine
CLI 下部に出る 1 行のステータス表示です。ブランチ名や環境マーカーを出しておくと、「dev に向いた環境で作業中」を見落としづらくなります。
{
"statusLine": {
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/statusline.sh"
}
}3.5 個人 override は settings.local.json
チーム共有 settings.json は最小公倍数で書きます。個人ごとに変えたい設定(モデル指定、特殊な permission の追加など)は settings.local.json に置き、こちらは gitignore します。雛形だけ settings.local.json.example としてコミットしておくと、新メンバーがコピーして使えます。
cp .claude/settings.local.json.example .claude/settings.local.json
# 自分用の設定を編集4. ガード層:hooks で「事故を未然に防ぐ」
hooks は .claude/hooks/*.sh の単なる shell スクリプトです。標準入力で JSON が来て、終了コードと標準エラー出力で Claude Code に応答します。
運用していて効いたのはこの 4 種類です。
- SessionStart: 起動時にブランチ・dirty 状態・ENV マーカーを 1 行で表示し、「いま自分が触っている環境」を視認できるようにする
- PreToolUse(Read):
.envやsecrets/を Read しようとしたら exit 2 で止める(permissions の deny を二重化) - PreToolUse(Bash):
terraform applyをterraform/devterraform/prodに対して打ったら止める、develop/mainへの force push を止める、rm -rf /系を止める - PostToolUse(Bash):
git commitの後でCo-Authored-By: Claude等の署名が混入していたら exit 2 で Claude に警告を渡し、amend を促す
設計上のポイントは「Claude に伝えたいときは exit 2」という入出力契約です。exit 2 の stderr は Claude に渡り、PreToolUse ならそのうえで tool も中止されます。PostToolUse は tool 実行後なので中止はできませんが、stderr は同じく Claude に届きます。一方 exit 1 は非ブロッキングで、stderr は transcript に出るもののユーザーにしか見えず、Claude には渡りません。
詳細な実装と落とし穴は連載第 2 回で深掘りします。ここでは「LLM への指示(CLAUDE.md)だけに頼らず、機械的に止められるところは止めておく」というスタンスだけ覚えておいてください。
5. 拡張層(その1):agents で「context を汚さず専門家に投げる」
agents/ に置くのは subagent の定義です。各エージェントは .md ファイルに YAML frontmatter + 本文(システムプロンプト)で書きます。
---
name: code-reviewer
description: Pre-PR code reviewer for this monorepo. Use proactively after a meaningful set of edits, before opening a PR, or when the user asks for a "review", "second opinion", or "is this safe to merge".
model: inherit
tools: Read, Grep, Glob, Bash
---
You are an experienced reviewer for this monorepo. You read the diff, then the surrounding code, then form an opinion.
## Scope of every review
- Correctness, stack-specific rules, security, test coverage
- Repo hard rules: no edits under terraform/dev/, no removed columns/tables/endpoints
## Workflow
1. Run `git diff origin/develop...HEAD` to see the change set.
2. For each non-trivial hunk, open the file and read at least 30 lines of context.
3. Group findings by severity (must-fix / should-fix / nit) with file:line citations.
4. End with a one-sentence verdict.運用していて重要だったのは次の 3 点です。
description がエージェント起動のトリガー文になる、ということ。メインの Claude が「いま subagent に投げるべきか」を判断するときに読むのは description です。ここに「いつ呼ぶべきか」を具体的に書けば書くほど、自動的に呼ばれる精度が上がります。逆にここが曖昧だと、せっかく定義しても呼ばれません。
tools で権限を絞ること。たとえばコードレビュー専門の agent には Write/Edit を渡しません。誤って修正してしまうリスクを物理的に消せます。
メインの context を汚さないこと。大量のファイルを読むレビュー作業をメインのスレッドで走らせると、その後の作業の context window を圧迫します。subagent に投げて、結果のサマリだけ受け取る運用にすると context が綺麗に保てます。
用途ごとに分けると、ある程度のサイズのプロジェクトでは次のような構成に落ち着きます。
code-reviewer— PR 前のコードレビューsecurity-reviewer— 秘密情報・IAM・LLM プロンプトインジェクションなど security 観点pipeline-debugger— 特定サブシステム(例:データパイプライン)の専門デバッガtest-writer— テストコードの追加
第 3 回で、それぞれの description と tools をどう設計するかを掘ります。
6. 拡張層(その2):commands と skills で「反復作業を 1 コマンドに」
最後の層は slash command と skill です。両方とも .md ファイル + frontmatter ですが、責務が違います。
6.1 slash command — 薄いラッパー
.claude/commands/lint.md のような薄いラッパーです。/lint と打つと中身が実行されます。
---
description: Run ruff lint + format check on the API package.
allowed-tools: Bash(make:*), Bash(poetry:*), Bash(ruff:*)
---
Run the project lint:
!`make lint`
If anything fails, summarize the diagnostics and propose specific fixes referencing file:line. Do not auto-format unless the user asks.本質的には「よく使うコマンド + 軽い指示」をまとめたショートカットです。/fmt, /test-api, /up, /down, /seed のような開発フローに 1 行で乗るものは全部 command に寄せます。
6.2 skill — 多段手順 + 文脈つき
一方 skill は「この手順を踏むときの判断基準・落とし穴・コマンド群」を 1 つの playbook としてまとめる単位です。たとえば「データパイプラインの A→B→C を一気通貫でトレースするとき、どの順序でどのログを見て、何があったら次にどうするか」を skill にします。
---
name: pipeline-trace
description: Trace a Pipeline A→B→C run end-to-end against the local docker-compose stack. Use when a document upload does not produce expected output and you need to localise the failure to A, B, or C.
allowed-tools: Bash(make:*), Bash(docker:*), Bash(curl:*), Bash(jq:*), Read, Grep
---
# Pipeline trace
You will follow one document through the integrated pipelines container.
## Pre-flight
... 以下、手順とトラブルシュート表もう 1 つ大事な属性が disable-model-invocation: true です。これを付けると「モデルが自動で発火することはなく、ユーザが明示的に呼んだときだけ動く」になります。破壊的操作を伴う skill(例:ローカル DB と Blob の wipe & re-seed)はこれを必ず付けます。
6.3 command と skill の境界線
だいたいこういう判断軸で分けます。
- 1 〜 2 行で済む / 副作用が小さい → command(例:
/lint,/fmt) - 多段手順がある / 判断分岐がある / 落とし穴を覚えておきたい → skill(例:パイプライントレース、リリースノート生成)
- 破壊的 / 明示呼び出し限定にしたい → skill +
disable-model-invocation: true
この境界線の話は連載第 4 回で詳しく扱います。
Tips
運用に入ってから効いたコツを並べておきます。
- deny は早めに広く貼る。「絶対やられたくないこと」を後回しにすると、必ず一度はやらかします
- hooks は短く、ブロック条件を明示する。長い hook はデバッグが大変な上、起動時に毎回走るので体感速度に効きます
- subagent の
descriptionに「いつ呼ぶか」を必ず書く。これがメイン Claude の判断材料です - command と skill を混ぜない。
/fooを打ったときに「単発実行なのか playbook なのか」が予測できなくなるとチームに浸透しません - 個人 override は
settings.local.json.exampleを一緒にコミットしておく。新メンバーが何を上書きしていいかが分かりません
まとめ
- Claude Code をチーム運用するなら
.claude/を「土台 → ガード → 拡張」の 3 層で設計するのが整理しやすい - 土台は settings.json(permissions / env / hooks 配線 / status line)
- ガード層は hooks(exit 2 で Claude に伝える / exit 1 はユーザー向けの警告、という入出力契約)
- 拡張層は agents(context を汚さない専門家) + commands(薄いラッパー) + skills(多段 playbook)
- LLM への指示(
CLAUDE.md)と機械的なガード(hooks / permissions)を二重化することで、確率的なすり抜けを防ぐ
次回予告
次回(第 2 回)は ガード層 = hooks に深く踏み込みます。
bash-guard.shで破壊コマンドを未然に止める実装secret-guard.shで.envRead を二重化する設計strip-claude-signature.shで commit 痕跡を検知する PostToolUse の使い方session-start.shで起動時に context を 1 行注入するパターン- 「exit 2 で Claude に伝える / exit 1 はユーザーにしか見えない」契約の落とし穴
ハンズオン形式で進めます。
参考リンク
更新履歴
- 3層モデルと見出しの層番号の矛盾を解消(第1層→ガード層、第2/3層→拡張層)。defaultMode: auto がプロジェクト設定で無視される点を注記。参考リンクを現行 URL に修正。誤字修正
- 記事内リンクの修正
- hook の exit code の説明を訂正(exit 2 = Claude に伝える、exit 1 = ユーザーにしか見えない)
この記事のタグ
Claude Code Harness
全4回Claude Code をチームで運用するための設計。共有する .claude/ の構成から、hook によるガード、subagent と skill の使い分けまで。


