メインコンテンツへスキップ
kt-tech.blog

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

設計18分で読めます

この記事でわかること

  • 個人設定では足りず、.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/prodterraform 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/devterraform/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 つに分かれていて、それぞれ役割が違います。

JSON
{
  "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 pushdocker volume rm などです。allow にすると怖い、deny にすると不便、という操作の置き場所です。

3.2 env

環境変数も settings.json で持てます。シークレットは入れず、プロジェクトを識別するための定数だけ持つのが安全です。

JSON
{
  "env": {
    "CLAUDE_CODE_PROJECT": "my-monorepo"
  }
}

3.3 hooks

hooks は次節で詳しく扱いますが、設定上は「どのイベントで」「どのスクリプトを呼ぶか」を配線するだけです。

JSON
{
  "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 に向いた環境で作業中」を見落としづらくなります。

JSON
{
  "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 としてコミットしておくと、新メンバーがコピーして使えます。

Bash
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): .envsecrets/ を Read しようとしたら exit 2 で止める(permissions の deny を二重化)
  • PreToolUse(Bash): terraform applyterraform/dev terraform/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 + 本文(システムプロンプト)で書きます。

Markdown
---
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 commandskill です。両方とも .md ファイル + frontmatter ですが、責務が違います。

6.1 slash command — 薄いラッパー

.claude/commands/lint.md のような薄いラッパーです。/lint と打つと中身が実行されます。

Markdown
---
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 にします。

Markdown
---
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.env Read を二重化する設計
  • strip-claude-signature.sh で commit 痕跡を検知する PostToolUse の使い方
  • session-start.sh で起動時に context を 1 行注入するパターン
  • 「exit 2 で Claude に伝える / exit 1 はユーザーにしか見えない」契約の落とし穴

ハンズオン形式で進めます。

参考リンク

更新履歴

  1. 3層モデルと見出しの層番号の矛盾を解消(第1層→ガード層、第2/3層→拡張層)。defaultMode: auto がプロジェクト設定で無視される点を注記。参考リンクを現行 URL に修正。誤字修正
  2. 記事内リンクの修正
  3. hook の exit code の説明を訂正(exit 2 = Claude に伝える、exit 1 = ユーザーにしか見えない)

この記事のタグ

Claude Code Harness

4

Claude Code をチームで運用するための設計。共有する .claude/ の構成から、hook によるガード、subagent と skill の使い分けまで。

  1. 1表示中【設計】Claude Code Harness 入門 — チームで共有する .claude/ の3層構成(連載 第1回)
  2. 2【実装】Claude Code の hook で事故を未然に防ぐ — PreToolUse / PostToolUse ガードの実装パターン(連載 第2回)
  3. 3【設計】Claude Code の specialized agent をどう切り分けるか — メイン context を汚さない subagent 設計(連載 第3回)
  4. 4【設計】Claude Code の skill と slash command の境界線 — 反復作業をどちらに寄せるか(連載 第4回)
この連載の一覧を見る