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 Codeのハーネスは下から積め——MCPから入って壊した3ヶ月で学んだ積み上げ順

0
Last updated at Posted at 2026-05-22

結論

  • Claude Codeの自律度は基本モデルではなくハーネス(ユーザー側設定)で決まる。下から積まないと崩れる
  • 積み順は CLAUDE.md / AGENTS.md → hooks → skills → sub-agents → MCP の5層。MCPから始めるのが一番壊れやすい
  • 「Claudeが急に頭悪くなった」と感じたら、まずCLAUDE.mdとhooksを見直す。MCPに手を入れても直らない

3ヶ月前、僕はMCPサーバーを4本同時に繋いで「これで最強」と思っていた。1セッションで使えるツールが17個から127個に膨らみ、Claude Codeが指示を忘れる、無関係な外部システムを叩く、で1週間後にはMCPを全部外していた。

参考にした一次資料:

順番を間違えるとハーネスは効かない。下から積めば効く。今日はその積み上げ順を、各層で何をやるか込みで全部書く。

目次

  1. なぜ「下から積む」か——MCPから入った僕の失敗
  2. 第1層: CLAUDE.md / AGENTS.md(最薄ルール層)
  3. 第2層: hooks(機械的強制層)
  4. 第3層: skills(再利用パターン層)
  5. 第4層: sub-agents(役割分離層)
  6. 第5層: MCP(外部接続層)
  7. 3ヶ月ごとの棚卸し——積み上げたものは腐る
  8. よくあるハマりポイント
  9. まとめ——今日/今週/今月のアクション

1. なぜ「下から積む」か——MCPから入った僕の失敗

2026年2月、Claude Codeを使い始めて1ヶ月で「MCP(Model Context Protocol)が公式機能だ」と知った。Slack、GitHub、Notion、Linear、4本のMCPサーバーをsettings.jsonに登録した(MCP公式リファレンス実装とサードパーティ製を混合)。

// 概念例 (パッケージ名はバージョンで変わるので公式リポ参照)
{
  "mcpServers": {
    "slack":  { "command": "npx", "args": ["-y", "<slack-mcp-package>"] },
    "github": { "command": "npx", "args": ["-y", "<github-mcp-package>"] },
    "notion": { "command": "npx", "args": ["-y", "<notion-mcp-package>"] },
    "linear": { "command": "npx", "args": ["-y", "<linear-mcp-package>"] }
  }
}

起動すると、Claude Codeは127個のツールを抱えた状態でセッションを始める(標準17個 + Slack 32個 + GitHub 41個 + Notion 19個 + Linear 18個)。「今日のSlack見て」と言うと、SlackじゃなくてNotionを叩いた。「GitHubのPR出して」と言うと、Linearにissueを切った。

実測で記録したのは以下のエラー:

Error: Unable to find issue with ID "PR-1234" in Linear workspace
(意図: GitHub PR #1234を確認したかった)
Error: Notion page not found: "today's standup"
(意図: Slackの今日のstandupチャンネルを見たかった)

50セッション分のログを取った結果、ツール選択をミスる頻度が31%(154回中48回)に達した。31%とは「3回に1回は意図と違う外部システムを叩く」レベルで、人間がフォローし続ける前提でないと回せない。MCPを全部外したらこの数字は0%に戻った。

そこから1ヶ月かけて、izanami氏とコムテ氏の運用記事(2026年5月)と、自分のCockpit実装ログを照合しながら、ハーネスを下から積み直した。結論はシンプルで、MCPは「すでに自律的に動けるハーネス」の最上位に乗せるもので、底から積まないと意味がない。

下記が積み順だ。

役割 厚さの目安
1. CLAUDE.md / AGENTS.md ルール・文脈 ルート薄/サブ厚
2. hooks 機械的強制 5〜15個
3. skills 再利用パターン 10〜25個
4. sub-agents 役割分離 5〜30個
5. MCP 外部接続 必要な分だけ

各層を順に見ていく。

2. 第1層: CLAUDE.md / AGENTS.md(最薄ルール層)

CLAUDE.mdはClaude Codeがセッション開始時に必ず読むファイル。~/.claude/CLAUDE.md(グローバル)と ./CLAUDE.md(プロジェクト)の両方が階層的に読まれる。

ルートCLAUDE.mdは薄く、サブディレクトリは厚く

最初に犯すミスは「ルートCLAUDE.mdに全部書く」だ。プロジェクト全体ルール・テストコマンド・リント設定・ドメイン知識を1ファイルに詰めると、無関係なファイル編集時にも全文が文脈に乗る。

解決はシンプルで、ルートは薄く、サブディレクトリで厚く書く

project/
├── CLAUDE.md                # 5〜10行。プロジェクト目的とルート規約のみ
├── frontend/
│   └── CLAUDE.md            # 80行。Next.jsの破壊的変更、テストコマンド、UI規約
├── backend/
│   └── CLAUDE.md            # 80行。Python設定、DB接続規約、テスト方針
└── ops/
    └── CLAUDE.md            # 40行。デプロイ手順、env変数規約

ルートCLAUDE.mdの中身は、例えばこれだけでいい:

# プロジェクト規約

- 各サブディレクトリのCLAUDE.mdを必ず読む
- main直push禁止、ブランチ経由でPR
- .env, credentials は絶対にコミットしない

作業はサブディレクトリで開始する

Claude Codeはインデックスを作らずgrepで探索する。深い場所で起動した方が、無関係ファイルへのノイズが減る。cd backend/ && claude の方が、ルートで起動して cd backend/ するより速い。

AGENTS.mdは何か

AGENTS.md はOpenAI Codex CLIなど他のエージェント型ツールと共通の規約ファイル。Claude Code単体ならCLAUDE.mdだけでいい。両方使うならCLAUDE.mdから @AGENTS.md で参照させる構造にすると重複が消える。

僕の環境では mine/CLAUDE.md@AGENTS.md 1行、mine/AGENTS.md も10行未満で運用している。

3. 第2層: hooks(機械的強制層)

CLAUDE.mdは「読めばわかる」のレイヤー、hooksは「自動で発火する」のレイヤー。CLAUDE.mdに書いても忘れることはあるが、hooksは確実に動く。

主要hookイベント

Claude Codeは公式ドキュメントで7種のhookイベントを公開している。

イベント 発火タイミング 用途例
SessionStart セッション起動時 git status表示、上限チェック
Stop Claudeが応答終了 URL校閲、終了通知
SubagentStop サブエージェント終了 レビュー結果集約
PreToolUse ツール実行直前 危険コマンド遮断
PostToolUse ツール実行直後 ログ記録、副作用検知
Notification 通知発生時 Mac通知転送
UserPromptSubmit プロンプト送信時 コンテキスト先送り

例: 危険コマンドをPreToolUseで遮断する

~/.claude/hooks/block-destructive.sh を作る:

#!/usr/bin/env bash
# PreToolUse hook: Bashツールの破壊的コマンドを遮断
set -euo pipefail

input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')

DANGER='rm -rf /|git push --force.*main|git reset --hard|drop database|truncate table'
if echo "$command" | grep -qE "$DANGER"; then
  echo '{"decision":"block","reason":"破壊的コマンドを検出。明示確認後に実行してください"}'
  exit 0
fi
echo '{}'

settings.json に登録:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/block-destructive.sh" }
        ]
      }
    ]
  }
}

CLAUDE.mdに「rm -rf 禁止」と書いても、4時間後のセッションで忘れることはある。hookなら忘れない。

Stop hookでURL校閲

外部URLを返す時、公開記事に死URLが混じるのは致命傷だ。Stop hookで直前応答を走査して死URLを警告する仕組みを入れている。

#!/usr/bin/env bash
# ~/.claude/hooks/check-urls.sh
last_response=$(cat)
urls=$(echo "$last_response" | grep -oE 'https?://[^ )"]+' | sort -u)

for url in $urls; do
  code=$(curl -sIL --max-time 5 -o /dev/null -w "%{http_code}" "$url")
  if [[ "$code" =~ ^(000|4[01]4|410|5..)$ ]]; then
    echo "⚠️ 死URL検出: $url ($code)" >&2
  fi
done

このhookが鳴ったら即座に応答を訂正する運用にしている。

hookの数の目安

僕の現在地は14個。多すぎると起動が遅くなり、少なすぎると強制力が弱い。**「3回同じミスをしたらhookにする」**ルールで運用している。

実測値:

  • hook 0個 → セッション起動 0.4秒
  • hook 14個(うちSessionStart 3個、PreToolUse 5個、Stop 2個、PostToolUse 4個)→ セッション起動 1.8秒
  • hook 30個まで増やした実験時 → セッション起動 4.3秒(体感で遅い)

20個を超えると体感が落ちる。15個前後が運用しやすい。

4. 第3層: skills(再利用パターン層)

skillsは「特定の指示パターンを名前で呼べる」機能で、公式ドキュメントに仕様がある。/qiita /verify /conductor のようにslash commandで呼ぶ、または自然言語からClaudeが自動起動する。

skill の構造

~/.claude/skills/<skill-name>/SKILL.md に置く。frontmatterに description を書くと、Claudeが「この依頼にこのskillが該当する」と自動判定する。

---
name: verify
description: 事実・URL・出典の検証専用スキル。応答に含める前にcurl死活+WebFetch内容確認を強制し、未検証主張に[unverified]タグを付ける。/verify で起動。
---

# verify スキル

このスキルが起動したら、現在の応答下書きに含まれる外部主張をすべて以下の手順で検証する:

1. 主張に出典URLが付いているか確認
2. URLに対してcurl -sIL --max-time 5 で死活確認
3. 200/301/302/307/308/403 以外は応答から削除
4. WebFetchで本文を確認、主張内容と一致しているか照合
5. 確認できない主張は [unverified] タグを付ける

このskillは「URL貼る前に校閲しろ」というルールをCLAUDE.mdに書くだけだと忘れがちな部分を、明示的に呼び出せる動作パターンに固定化している。

skillの粒度

  • 「同じ手順を3回以上やる」→ skill化
  • 「手順が10ステップ以上ある」→ skill化
  • 「ミスると致命的(公開記事/本番デプロイ)」→ skill化

僕の現在地は21個。多いが、それぞれ役割が明確で重複していない。

よくあるアンチパターン

  • 1スキルに5つの目的を混ぜる → 起動条件が曖昧になり自動起動が効かない
  • frontmatter description が抽象的 → Claudeが「該当する」と判定できない
  • skill同士で同じファイルを書く → 衝突して結果が不安定

5. 第4層: sub-agents(役割分離層)

Claude Codeは複数のsub-agentを切り替えて使う構造を持つ(公式ドキュメント — Sub-agents)。~/.claude/agents/<agent-name>.md に役割定義を書く。

sub-agentの基本パターン

---
name: reviewer
description: コードレビュー・軽微な修正・git commit・PR作成を行うエージェント
tools: Read, Grep, Glob, Bash, Edit
---

あなたはコードレビュー専門のエージェントです。

- 関数50行以下、ネスト3階層以下を確認
- 型ヒント/型定義の漏れを指摘
- セキュリティ問題(SQL injection, XSS, command injection)を最優先で検出
- レビュー後、軽微な修正は自分で行い、git commitまで実行

役割分離の効用

1人のClaudeが「設計+実装+レビュー+デプロイ」を全部やると、視点が混じってレビューが甘くなる。レビューだけのsub-agentを別に立てると、実装した本人とは違う視点で見れる。

僕の構成例:

エージェント 役割
builder 設計・実装・テスト・バグ修正
reviewer レビュー・軽微な修正・PR作成
researcher 技術調査・コード編集はしない
team-lead タスクを受け取り、最適なエージェントを選んで実行
Explore 読み取り専用・ファイル/シンボル検索
Plan 実装計画の設計のみ

team-leadがオーケストレーター役で、必要に応じて他のエージェントを並列起動する。これだけで、1セッションで「並列調査→設計→実装→レビュー」が回る。

並列起動のコツ

複数sub-agentを起動する時、独立したタスクは並列で投げる。1つのメッセージで複数の Agent tool call を出す。直列に投げると、同じ調査を待ち時間付きで繰り返すことになる。

僕が自作した並列オーケストレーターでは、5本のリサーチを並列実行することで、直列17分→並列2分53秒、つまり5.9倍速を実測した。同じパターンはsub-agentでもそのまま効く。

6. 第5層: MCP(外部接続層)

ここでようやくMCPだ。MCPはClaude Code外部のシステム(Slack、GitHub、Notion、Figma、各種DB)に標準プロトコルで接続する。

MCPを後回しにする理由

MCPはツールを増やすが、ツールが増えるとClaudeの判断ノイズも増える。1セッションでMCPツールが127個ある状態は、人間で言うと「机に127個の道具が並んでいる」状態で、選択ミスが必ず起きる。冒頭で書いた31%の選択ミス率が実測値だ。

下4層が整っていれば、「Slackに送るときはこの順序、GitHubにPR出す時はこの規約」が固定化されている。MCPはその規約の上に乗るだけで、選択の余地は減る。

MCP導入の判断基準

  • その外部システムを週3回以上叩くか? → Yes なら導入
  • CLAUDE.mdに「この外部システムを使う時の規約」が書けているか? → Yes なら導入
  • 対応するhookやskillで規約を強制できるか? → Yes なら導入

3つとも No なら導入を見送る。週1しか使わないNotionをMCPで繋いでも、毎セッションで120ツールのノイズを払うコストの方が高い。

自作MCPの空白

僕自身、MCPは消費(既製サーバーの利用)だけで、自作経験ゼロだ。これがハーネスの最上位の空白で、次の積み上げ対象になっている。FastMCPなどPythonベースのフレームワークでHello Worldまでは動くが、本番運用に乗せた経験はまだない。

ここを埋めるとハーネスは完成するが、下4層が整っていない状態で自作MCPに飛ぶのは、また同じ失敗を繰り返すだけだ。

7. 3ヶ月ごとの棚卸し——積み上げたものは腐る

ハーネスは積み上げるだけでなく、定期メンテナンスが必要だ。理由は3つ。

  1. 古い記述と新運用の矛盾: 3ヶ月前のCLAUDE.mdに「Aを使え」、6ヶ月前のskillに「Bを使え」と書かれていると、Claudeが古い側を採用して挙動が劣化する
  2. 発火していないhookが残る: 1度しか使わなかったhookが、起動コストを払い続ける
  3. 重複するskillが増える: qiita-postqiita-publishqiita-auto-draft が並走していて、どれを起動するかでClaudeが迷う

棚卸しチェックリスト

カテゴリ チェック項目
CLAUDE.md / AGENTS.md 300行超なら冗長/矛盾チェック。ルートは薄く保たれているか
hooks 過去3ヶ月で1度もfireしてないhookを削除候補に
skills 過去6ヶ月で起動0回のskillを削除候補に
agents 役割の重複(reviewer/qa/lint等)を統合
memory 矛盾するfeedback同士を統合、古い記述を更新
MCP 使ってないMCPは settings.json から外す

実行コマンド

# memory の最終更新日順
ls -lat ~/.claude/projects/*/memory/*.md | tail -20

# 過去N日で発火していないhookを検出(jsonlログ前提)
find ~/.claude/cockpit/hooks -name "*.jsonl" -mtime -90 | \
  xargs cat | jq -r .type | sort | uniq -c

棚卸し結果を harness_audit_YYYY_MM.md として残すと、削除/統合した変更履歴になる。

僕の次回棚卸し予定は2026年8月。3ヶ月先に /schedule で登録した。

8. よくあるハマりポイント

実際に踏んだ落とし穴を5つ。

ハマり1: グローバルCLAUDE.mdに全部書いて遅くなる

~/.claude/CLAUDE.md を412行にして、毎セッションでフルロードしていた時期がある。1セッションあたり推定3,200トークン(全文約12KB)が毎回前置で流れ、関係ないルールが文脈に乗り続けた。

対処: グローバルは「全プロジェクト共通の絶対ルール」のみ(50〜100行・約800〜1,200トークン)、各プロジェクトは ./CLAUDE.md で固有規約を書く。僕の現在は78行で運用していて、起動時間が体感で半分以下になった。

ハマり2: hookがJSON出力規約を破ってサイレント失敗

hookの出力はJSONで、{"decision": "block", "reason": "..."} のような構造を返す。printfで Error: ... のような生文字列を吐くと、Claude Code側で無視されて、hookが効いていないように見える。

実際に踏んだエラー:

$ ./block-destructive.sh < test-input.json
Error: command blocked
$ echo $?
0
# Claude Code側は無視。jqで検証するとパース失敗していた
$ ./block-destructive.sh < test-input.json | jq .
parse error: Invalid numeric literal at line 1, column 6

対処: hookの出力は必ずjqで検証してから本番投入する。

echo '{"decision":"block","reason":"test"}' | jq . > /dev/null && echo OK

ハマり3: skillのdescriptionが抽象的で自動起動しない

description: "便利な機能" と書いても、Claudeはどの依頼で起動すべきか判定できない。

対処: descriptionに 「いつ起動するか」を具体的に書く

description: "Qiita記事を自動生成・投稿するスキル。
ネタ選定→調査→執筆→品質ゲート→投稿までの一気通貫パイプライン。
『記事書いて』『Qiita投稿』で起動。"

トリガーになる発話の例まで書くと、自動起動の精度が上がる。

ハマり4: sub-agentに大量のtoolsを付与してコンテキスト爆発

tools: * で全ツールを許可すると、エージェントが本来の役割から逸脱する。reviewerエージェントがWebFetchで関係ないサイトを見に行き始めたこともある。

対処: 各エージェントの tools: をホワイトリストにする。reviewerなら Read, Grep, Glob, Bash, Edit のみ、researcherなら Read, Grep, Glob, WebSearch, WebFetch のみ。

ハマり5: MCP導入で既存hookが効かなくなる

MCPツールは独自プロトコルで動くため、Bash用に書いたPreToolUse hookは発火しない。MCPで notion_create_page を叩いた時の安全確認hookは別に用意する必要がある。

対処: MCPツール用のmatcherを別に書く。

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [...] },
      { "matcher": "mcp__notion__.*", "hooks": [...] }
    ]
  }
}

9. まとめ——今日/今週/今月のアクション

ハーネスは下から積む。MCPから入るな。

今日(30分)

  • ~/.claude/CLAUDE.md を開き、行数を確認する。300行超なら冗長部分を切る候補に印を付ける
  • プロジェクトに ./CLAUDE.md が無ければ、5行で作る(プロジェクト目的・テストコマンド・1つの絶対ルール)

今週(2時間)

  • ~/.claude/hooks/ に最低1つhookを置く。block-destructive.sh が一番効きやすい
  • 既存skillの description を見直す。「いつ起動するか」が書かれていないskillは書き直す

今月(半日)

  • sub-agentを役割別に最低3個(builder / reviewer / researcher)作る
  • MCPを使っているなら、各MCPに対応するhookまたはskillで規約を1つ書く
  • 棚卸し予定日を /schedule または カレンダーに3ヶ月後で登録する

ハーネスは「組んだら終わり」ではない。3ヶ月後にまた壊れる。そのつもりで運用する。


僕自身、自作MCPがまだ書けていない。次の3ヶ月で1本書ければ、ハーネス5層が全部埋まる。書けたらまた記事にする。

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?