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を「もう一人の開発者」にする — agents / skills / hooks を駆使した自動化パイプライン構築記

0
Last updated at Posted at 2026-03-11

Claude Codeを「もう一人の開発者」にする — subagents / skills / hooks を駆使した自動化パイプライン構築記

はじめに

Claude Code、使ってますか?

「AIにコード書かせるツールでしょ?」——半分正解で、半分もったいない。Claude Codeには subagents(サブエージェント)、skills(ドメイン知識・ワークフロー)、hooks(自動トリガー)、rules(常時適用ルール) といった拡張機構が揃っていて、これらを組み合わせると「AIをチームメンバーとして組み込んだ開発パイプライン」が作れます。

この記事では、私が実際過去に構築・運用していた個人プロジェクト——**「Webサービスの通知応答自動化システム」**を題材に、Claude Codeの拡張機構をフル活用する方法を紹介します。


何を作ったのか(30秒で)

あるWebサービスに届く通知やメッセージに対して、半自動で応答を生成・送信するシステムです。

[1] Scrape  → Webから受信メッセージを収集
[2] Generate → Claude Codeが応答を自動生成
[3] Review   → 別のサブエージェントが品質チェック
[4] Approve  → ダッシュボードで人間が最終確認
[5] Send     → 承認済みメッセージを自動送信

技術スタックはシンプル:

技術 役割
Playwright Webスクレイピング・自動操作
better-sqlite3 ローカルDB
Express + SSE リアルタイムダッシュボード
Claude Code メッセージ生成・コードレビュー
p-limit 並列制御

面白いのは**Claude Codeが「開発の補助」ではなく「パイプラインの中で実際にタスクを実行する」**という点です。


全体アーキテクチャ

┌─────────────────────────────────────────────────┐
│                   Claude Code                    │
│  ┌────────────┐ ┌──────────┐ ┌───────────────┐  │
│  │ subagents/ │ │  skills/ │ │    hooks/     │  │
│  │ ・生成     │ │ ・ペルソナ│ │ ・auto-format │  │
│  │ ・レビュー │ │ ・ルール │ │ ・console.log │  │
│  │ ・コード   │ │ ・ドメイン│ │ ・mainブランチ│  │
│  └────────────┘ └──────────┘ └───────────────┘  │
└──────────┬──────────────────────────────────────┘
           │ spawn (Node.js child_process)
           ▼
┌─────────────────────────────────────────────────┐
│              Application Pipeline                │
│                                                  │
│  Scrape ──→ Generate ──→ Review ──→ Send        │
│  (Playwright) (Claude CLI) (Subagent)(Playwright)│
│       │          │          │          │         │
│       ▼          ▼          ▼          ▼         │
│  ┌──────────────────────────────────────────┐   │
│  │          SQLite (better-sqlite3)          │   │
│  │  contacts │ messages │ replies            │   │
│  └──────────────────────────────────────────┘   │
│                      │                           │
│                      ▼                           │
│  ┌──────────────────────────────────────────┐   │
│  │    Dashboard (Express + SSE)              │   │
│  │    承認 / 却下 / リアルタイム監視          │   │
│  └──────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

1. Claude Codeの拡張機構を理解する

まず、Claude Codeの主要な拡張ポイントを整理します。

補足: 以前は「slash commands」が独立した概念でしたが、現在は skills に統合 されています。.claude/commands/deploy.md.claude/skills/deploy/SKILL.md はどちらも /deploy コマンドになり、同じように動作します。既存の commands ファイルは後方互換で動きますが、新規作成は skills で行うのが推奨です。

subagents — 専門家を定義する

.claude/agents/
├── reply-generator.md   # メッセージ生成の専門家
├── reply-reviewer.md    # 品質チェックの専門家
└── code-reviewer.md     # コードレビューの専門家

subagentは「特定のタスクに特化したClaude」です。独立したコンテキストウィンドウを持ち、使えるツール、振る舞いのルールをMarkdownのYAMLフロントマターで定義します。

---
name: reply-generator
description: 応答メッセージ生成専門家。段階的推論で分析・生成。
tools: Read, WebSearch
model: sonnet
---

# 応答メッセージ生成

## 生成フロー

### Step 1: コンテキスト分析
- 送信者の属性・過去のやりとりを把握
- メッセージ履歴の流れを確認
- 相手の意図・要求レベルを判定

### Step 2: 応答戦略の選択
- やりとりの段階判定(初回/継続/クロージング)
- 適切なトピック・情報の選択
- 文量・トーンの調整

### Step 3: ルール確認
- 禁止事項の再確認
- 事実誤認防止チェック

### Step 4: メッセージ生成
- サービスのトーンに合った自然な文体
- 適切な情報量
- 必要なアクションの提示

ポイントは subagent が定義上すでに独立コンテキストで動作するという点です。親セッションの会話履歴を引きずらず、与えられたシステムプロンプトとタスクだけで動作します。タスク完了後、親セッションには最終メッセージのみが返されます。

注意: subagent のフロントマターに書ける skills フィールドで、参照するスキルを指定できます。これにより、サブエージェントは特定の知識ベースを参照しながら動作します。

skills — ドメイン知識・ワークフローをモジュール化する

.claude/skills/
├── sender-profile/SKILL.md       # 送信者のコンテキスト情報
├── reply-rules/SKILL.md          # メッセージ生成の絶対ルール
├── playwright-patterns/SKILL.md  # Playwright自動化パターン集
├── agent-teams/SKILL.md          # マルチエージェント協調パターン
└── ...

skillはSKILL.mdファイルとYAMLフロントマターで構成されます。Claude Codeは起動時に各skillのnameとdescriptionだけをシステムプロンプトに読み込み、タスクに関連すると判断した場合にのみ本文を読み込みます(プログレッシブ・ディスクロージャー)。これによりコンテキストウィンドウを節約できます。

disable-model-invocation: true を指定すると、Claudeが自動的にそのskillを呼び出すことを防止できます。ユーザーが /skill-name で手動呼び出しすることは引き続き可能です。デプロイや送信など、タイミングを人間がコントロールしたい操作に便利です。

---
name: playwright-patterns
description: Playwrightスクレイピングのベストプラクティス。Playwrightを使ったコードを書く際に参照。
disable-model-invocation: true
---

# Playwrightパターン集

## ページ遷移
```javascript
// クリック + URL変更待機
await Promise.all([
  page.waitForURL('**/target/**', { timeout: 30000 }),
  element.click(),
]);

並列処理

const pLimit = require('p-limit');
const limit = pLimit(50);

await Promise.all(
  items.map(item => limit(async () => {
    const page = await context.newPage();
    try {
      await processItem(page, item);
    } finally {
      await page.close();
    }
  }))
);

**これが地味にめちゃくちゃ便利。** Playwrightのコードを書く際、`/playwright-patterns` で明示的に呼び出せば、Claude Codeが「うちのプロジェクトのPlaywrightパターン」を知った状態で提案してくれます。

> **補足:** skillを「参照専用の知識ベース」として使いたい場合は、skillディレクトリ内に `references/` フォルダを作り、SKILL.md本文から `@references/xxx.md` のように参照する設計が推奨されています。

### hooks — 自動で品質を担保する

hooks は Claude Code のライフサイクルイベント(ツール実行前後など)で自動実行されるシェルコマンドです。**確定的(deterministic)**に動作し、Claudeの判断に依存しません。

現在、Claude Codeは**12のライフサイクルイベント**をサポートしています。主要なものは以下の通りです:

| イベント | タイミング | 用途 |
|----------|-----------|------|
| PreToolUse | ツール実行前 | 危険な操作のブロック、入力の書き換え |
| PostToolUse | ツール実行後 | 自動フォーマット、フィードバック |
| UserPromptSubmit | プロンプト処理前 | バリデーション、コンテキスト注入 |
| SessionStart / SessionEnd | セッション開始/終了 | 環境セットアップ、ログ、クリーンアップ |
| Stop / SubagentStop | 応答/サブエージェント完了時 | 完了制御、通知 |
| Notification | 通知送信時 | デスクトップ通知など |
| PreCompact | コンパクション前 | コンテキスト圧縮時の制御 |
| PermissionRequest | 権限ダイアログ表示時 | 自動的な許可/拒否 |
| PostToolUseFailure | ツール実行失敗時 | エラーログ、アラート |
| ConfigChange | 設定変更時 | 設定変更の検知 |

> **補足:** hookのハンドラータイプは4種類あります。
> - **`command`**: シェルコマンドを実行(本記事で主に解説)
> - **`prompt`**: Claudeモデルにシングルターン評価を行わせる(正規表現では難しいセマンティックな条件判定に便利)
> - **`agent`**: サブエージェントを起動し、Read/Grep/Globなどのツールを使った深い検証を行う
> - **`http`**(2026年2月追加): 外部エンドポイントにPOSTしてJSON応答を受け取る
>
> 本記事では最も基本的な `command` タイプを中心に解説します。

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-main-push.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/auto-format.sh"
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/detect-console-log.sh"
          }
        ]
      }
    ]
  }
}

重要: matcher フィールドはツール名のシンプルな文字列マッチです(例:"Bash""Edit|Write")。tool_inputの中身を検査したい場合は、hookのコマンド/スクリプト内でstdinから受け取るJSONをパースして判定します。

mainブランチ保護hookの実装例(.claude/hooks/block-main-push.sh):

#!/bin/bash
set -euo pipefail

# stdinからJSON入力を読み取り、コマンドを抽出
command=$(jq -r '.tool_input.command // ""' < /dev/stdin)

# git push/merge/rebase で main を含むコマンドをブロック
if echo "$command" | grep -Eq 'git\s+(push|merge|rebase).*main'; then
  echo "BLOCKED: mainブランチへの直接操作は禁止です。featureブランチを使ってください。" >&2
  exit 2  # exit 2 = ブロック
fi

exit 0  # exit 0 = 許可

auto-format hookの実装例(.claude/hooks/auto-format.sh):

#!/bin/bash
set -euo pipefail

# stdinからJSONを読み取り、ファイルパスを抽出
file_path=$(jq -r '.tool_input.file_path // .tool_input.path // ""' < /dev/stdin)

# .jsファイルの場合のみPrettierを実行
if echo "$file_path" | grep -q '\.js$'; then
  npx prettier --write "$file_path" 2>/dev/null || true
fi

exit 0

console.log検出hookの実装例(.claude/hooks/detect-console-log.sh):

#!/bin/bash
set -euo pipefail

file_path=$(jq -r '.tool_input.file_path // .tool_input.path // ""' < /dev/stdin)

if echo "$file_path" | grep -q '\.js$'; then
  if grep -n 'console\.log' "$file_path" 2>/dev/null; then
    echo "WARNING: console.logが検出されました" >&2
    # exit 0 なので警告のみ(ブロックはしない)
  fi
fi

exit 0

3つのガードレール:

Hook タイミング 効果
mainブランチ保護 PreToolUse exit 2 でコマンドをブロック
auto-format PostToolUse .js 編集後に自動Prettier
console.log検出 PostToolUse デバッグログの残置を警告

Exit codeの違いは超重要:

  • exit 0: 成功(操作続行)
  • exit 2: ブロック(PreToolUseの場合、ツール呼び出しをキャンセル。stderrのメッセージがClaudeにフィードバックされる)
  • exit 1: 非ブロッキングエラー(stderrはverboseモードで表示されるが、操作は続行される

セキュリティhookで exit 1 を使うと一切ブロックできないので要注意。

より高度な制御(hookSpecificOutput):

exit codeによる制御はシンプルですが、PreToolUseではJSON出力によるより細かい制御も可能です。hookSpecificOutput を使うと、ブロック/許可だけでなく、ユーザーへの確認エスカレーション(askツール入力の書き換え(updatedInputClaudeへの追加コンテキスト注入(additionalContext ができます。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed"
  }
}

以前のトップレベルの decision / reason フィールドはPreToolUseでは非推奨となっており、hookSpecificOutput の使用が推奨されています。本記事のexit code方式も引き続き動作しますが、複雑な制御が必要な場合はJSON出力方式を検討してください。

rules — 常時適用のコーディング規約

.claude/rules/
├── coding-style.md    # コーディングスタイル
├── development.md     # 開発ルール
├── agent-teams.md     # マルチエージェント協調ルール
└── message-rules.md   # メッセージ生成の絶対ルール

rulesはClaude Codeが常にシステムプロンプトに読み込むルールファイルです。paths フロントマターで適用範囲を限定できます。

---
paths:
  - "src/**/*.js"
---

# コーディングスタイル

## イミュータビリティ(重要)

オブジェクトは常に新規作成、変更禁止:

```javascript
// NG: ミューテーション
function updateUser(user, name) {
  user.name = name
  return user
}

// OK: イミュータブル
function updateUser(user, name) {
  return { ...user, name }
}

関数設計

  • 1関数1責務(50行以下)
  • 早期リターンでネスト削減

これにより、Claude Codeが書くコードは**プロジェクトのスタイルに自動的に準拠**します。

### 拡張ポイントの使い分け(判断フレームワーク)

| 機構 | 性質 | 用途 |
|------|------|------|
| CLAUDE.md / rules | 確定的(常時読み込み) | プロジェクトの記憶、コーディング規約 |
| hooks | 確定的(イベント駆動) | 品質ゲート、自動フォーマット、通知 |
| skills | 確率的(Claudeの判断) | ドメイン知識、ワークフロー、カスタムコマンド |
| subagents | 確率的(委譲) | 独立コンテキストでの専門タスク |

**「100%守らせたいルール」にはhooks、「Claudeの判断に委ねてよいもの」にはskills。** この使い分けが設計の鍵です。

---

## 2. AIをパイプラインに組み込む:Node.jsからClaude CLIをspawnする

ここからが本番。Claude Codeを「開発ツール」ではなく**「パイプラインのワーカー」として使う**パターンです。

### アーキテクチャ

```javascript
const { spawn } = require('child_process');

function runGenerateCommand(targetId, existingSessionId) {
  return new Promise((resolve, reject) => {
    // セッション継続の場合は --resume を使用
    const args = ['-p', `/generate-reply ${targetId}`, '--output-format', 'json'];

    if (existingSessionId) {
      args.push('--resume', existingSessionId);
    }

    const claude = spawn('claude', args, {
      shell: false,
      stdio: ['pipe', 'pipe', 'pipe'],
      cwd: projectRoot,
    });

    claude.stdin.end();

    let output = '';
    claude.stdout.on('data', (data) => { output += data.toString(); });

    const timeout = setTimeout(() => {
      claude.kill('SIGKILL');
      reject(new Error('Timeout'));
    }, 300000); // 5分

    claude.on('close', (code) => {
      clearTimeout(timeout);
      if (code !== 0) {
        reject(new Error(`Claude failed (code ${code})`));
        return;
      }

      try {
        // --output-format json の場合、session_id と result が返る
        const json = JSON.parse(output);
        const sessionId = json.session_id;

        // マーカーで出力からメッセージ本文を抽出
        const match = json.result.match(/---REPLY_START---([\s\S]*?)---REPLY_END---/);
        const message = match ? match[1].trim() : json.result.trim();
        resolve({ sessionId, message });
      } catch (e) {
        // JSONパース失敗時はテキストとして処理
        const match = output.match(/---REPLY_START---([\s\S]*?)---REPLY_END---/);
        const message = match ? match[1].trim() : output.trim();
        resolve({ sessionId: null, message });
      }
    });
  });
}

ポイント解説

1. セッション管理

Claude CLIの --continue--resume を使い分けます。

# 初回実行
claude -p "/generate-reply target123" --output-format json

# 直前のセッションを継続
claude -p "/generate-reply target123" --continue

# 特定のセッションを再開(session_idはJSON出力から取得)
session_id=$(claude -p "/generate-reply target123" --output-format json | jq -r '.session_id')
claude -p "追加の修正をお願いします" --resume "$session_id"

--resume するとClaude Codeは前回の会話コンテキストを保持した状態で動きます。つまり、同じ相手との過去のやりとりを踏まえた応答が生成できます。セッションIDはDBに保存しておきます。

2. 出力のパース(マーカー方式)

Claude Codeの出力には生成テキスト以外にもログやメタ情報が含まれるため、マーカーで囲んで抽出します。

---REPLY_START---
承知しました。詳細を確認して折り返しますね。
---REPLY_END---

skill側でこのフォーマットを強制しています:

## 出力

以下のフォーマットで応答本文のみを出力する。

---REPLY_START---
{応答本文}
---REPLY_END---

Tips: --output-format json を使うと result フィールドにテキスト結果が入るので、パースがより安定します。

3. 並列実行とロック回避

const CONCURRENCY = 20;
const SPAWN_STAGGER_MS = 1000;

targets.map((target, index) =>
  pLimitInstance(async () => {
    // 起動タイミングをずらしてファイルロック競合を回避
    if (index > 0) {
      await new Promise((r) => setTimeout(r, SPAWN_STAGGER_MS * index));
    }
    // ...
  })
);

Claude CLIは内部で ~/.claude.json にアクセスするため、同時に大量spawnするとファイルロック競合が発生します。1秒ずつスタガーすることで安定動作を実現しました。これは実際にハマったポイントです。


3. マルチエージェント品質チェック

生成したメッセージは、別のサブエージェントが独立コンテキストでレビューします。

---
name: reply-reviewer
description: 生成された応答メッセージをレビュー。品質・事実性・ルール準拠を検証。
tools: Read
model: sonnet
---

# チェック観点(すべて確認)

1. 文脈の整合性: 受信メッセージの要点すべてに応答しているか
2. 事実チェック: 提供可能な情報の範囲を逸脱していないか
3. 禁止事項: ポリシーに反するアクションを取っていないか
4. 分量: 受信メッセージの量感に対して適切か
5. トーン: サービスの文脈に合った表現になっているか
6. 自然さ: やりとりとして自然か
...(全10項目)

サブエージェントは定義上すでに独立したコンテキストウィンドウで動作します。生成側の思考過程に引きずられず、純粋にメッセージの品質だけを評価できます。

注意: 以前の版でsubagentのフロントマターに context: fork と書いていましたが、これはskillのフロントマターで使うフィールドです。subagent(.claude/agents/ のファイル)は設計上すでに独立コンテキストなので不要です。skillに context: fork を付けた場合、そのskillがサブエージェントとして独立コンテキストで実行される仕様ですが、2026年1月時点のGitHub issue(#17283)によると、Skill tool経由での呼び出し時に context: fork が無視されメインコンテキストで実行されるバグが報告されています。独立コンテキストでの実行が必要な場合は、skillではなくsubagent(.claude/agents/)として定義するのが確実です。

生成サブエージェント (reply-generator)
  │
  │ "このメッセージどうですか?"
  ▼
レビューサブエージェント (reply-reviewer)  ← 独立コンテキスト(subagentの標準動作)
  │
  ├── PASS → DBに保存、ダッシュボードで承認待ち
  └── FAIL: "要求への回答が不足" → 生成サブエージェントが修正して再レビュー

4. コードレビューもサブエージェントに

開発中のコード品質もサブエージェントが担保します。

---
name: code-reviewer
description: コードレビュー専門家。品質・セキュリティ・保守性を検証。
tools: Read, Grep, Glob, Bash
model: sonnet
---

## セキュリティ(重大)
- ハードコードされた認証情報
- SQLインジェクションリスク
- パストラバーサル

## コード品質(高)
- 50行超の関数
- 800行超のファイル
- 4層超のネスト
- エラーハンドリング欠如
- console.log残置
- ミューテーションパターン

## 判定
- APPROVE: CRITICAL/HIGHなし
- WARNING: MEDIUMのみ
- BLOCK: CRITICAL/HIGHあり → マージ不可

コード変更後にこのサブエージェントを走らせると、セキュリティリスクからスタイル違反まで自動チェックしてくれます。 BLOCK 判定が出ると具体的な修正方法まで提示してくれるのが便利。


5. DB設計:WALモードとプリペアドステートメント

SQLiteを「ちゃんと」使うポイントも紹介します。

const db = require('better-sqlite3')('data/app.db');

// パフォーマンス最適化
db.pragma('journal_mode = WAL');        // 並列読み書き
db.pragma('synchronous = NORMAL');      // 書き込み高速化
db.pragma('cache_size = -64000');       // 64MBキャッシュ
db.pragma('temp_store = MEMORY');       // テンプ領域をメモリに
db.pragma('mmap_size = 268435456');     // 256MB mmap

スキーマバージョニング

const SCHEMA_VERSION = 8;

function migrate(db) {
  const currentVersion = db.pragma('user_version', { simple: true });

  if (currentVersion < 7) {
    // v7: テーブル統合マイグレーション
    db.transaction(() => {
      db.exec(`ALTER TABLE contacts ADD COLUMN ...`);
      db.exec(`INSERT INTO contacts SELECT ... FROM legacy_entries`);
      db.exec(`DROP TABLE legacy_entries`);
    })();
  }

  if (currentVersion < 8) {
    // v8: キャッシュカラム追加
    db.transaction(() => {
      db.exec(`ALTER TABLE contacts ADD COLUMN cached_data BLOB`);
    })();
  }

  db.pragma(`user_version = ${SCHEMA_VERSION}`);
}

SQLiteの user_version プラグマをバージョン管理に活用。マイグレーションファイルを別途管理する必要がなく、コードとスキーマが常に同期します。


6. Playwrightで50並列スクレイピング

const pLimit = require('p-limit');
const CONCURRENCY = 50;
const limit = pLimit(CONCURRENCY);

async function scrapeAll(context, targetIds) {
  return Promise.all(
    targetIds.map(targetId =>
      limit(async () => {
        const page = await context.newPage();
        try {
          await page.goto(`${BASE_URL}/inbox/${targetId}`);
          const messages = await extractMessages(page);
          return { targetId, messages };
        } catch (err) {
          console.error(`Failed: ${targetId}`, err.message);
          return { targetId, messages: [], error: err.message };
        } finally {
          await page.close();
        }
      })
    )
  );
}

p-limit + Playwrightのcontext.newPage() の組み合わせが強力。1つのブラウザコンテキスト(= 1セッション)から50タブを同時に開いて並列スクレイピングします。タブごとに page.close() を確実に呼ぶのがポイント。


7. ダッシュボード:SSEでリアルタイム監視

const express = require('express');
const app = express();

// SSE接続プール
const sseClients = [];

app.get('/api/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  });
  sseClients.push(res);
  req.on('close', () => {
    const index = sseClients.indexOf(res);
    if (index >= 0) sseClients.splice(index, 1);
  });
});

// パイプラインの状態変更を全クライアントにブロードキャスト
function broadcast(event, data) {
  sseClients.forEach(client => {
    client.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
  });
}

スクレイピング、メッセージ生成、送信——各パイプラインの進捗がリアルタイムでダッシュボードに反映されます。WebSocketではなくSSEを選んだのは、サーバーからの一方向通知で十分だから。承認・却下のアクションは通常のPOSTで処理します。


8. Agent Teams:マルチエージェント協調開発

Claude Codeの Agent Teams も活用しています。

⚠️ 実験的機能: Agent Teamsは実験的(experimental)機能であり、デフォルトでは無効です。使用するには settings.json または環境変数で CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS を有効にする必要があります。セッション再開、タスク調整、シャットダウン動作に既知の制限があります。Claude Code v2.1.32以降が必要です。

Agent Teamsを使うと、複数のClaude Codeインスタンスがチームとして並列作業できます。サブエージェントとの違いは、Agent Teamsでは各メンバーがファイルシステムベースのinbox(~/.claude/teams/{team}/inboxes/)を介して互いにメッセージを送り合える点です(サブエージェントは親に結果を返すのみで、互いに直接通信できません)。共有タスクリスト、依存関係の追跡、タスクの自動アンブロックなども組み込まれています。

Plan-Then-Implement パターン

最も効果的だったパターンがこれ:

Step 1: リーダー(Opus)が計画を策定
  ├── コードベース全体を探索
  ├── 実装計画を作成
  └── 人間がレビュー

Step 2: チームメイトに計画を分配して並列実行
  ├── チームメイトA: src/message/ を担当
  ├── チームメイトB: src/db/ を担当
  └── チームメイトC: src/dashboard/ を担当

衝突回避ルール

## 衝突回避
- 1ファイル = 1オーナー。複数チームメイトが同一ファイルを編集しない
- Spawn Promptで「編集するファイル」「触れないファイル」を明示する

これをrules/に書いておくと、Claude Code自身がファイルの所有権を意識して作業してくれます。

敵対的デバッグ

バグ調査では「敵対的デバッグ」が強力でした:

5人のチームメイトを起動し、それぞれ異なる仮説を検証:
- 仮説A: セッション切れが原因
- 仮説B: CSSセレクタの変更が原因
- 仮説C: タイミング依存のレースコンディション
...

証拠を見つけたら他の仮説の担当者にSendMessageで共有し、
相互に反論させる。

1人のAIだと最初に思いついた仮説に引っ張られがちですが、複数で相互検証すると根本原因に早く到達できます。

注意: Agent Teamsはトークン消費が大きいです。各チームメイトは独立したフルのClaude Codeセッション(独自のコンテキストウィンドウ)なので、3チームメイトのチームは単一セッションの3〜4倍のトークンを消費します。並列化の恩恵が明確なタスクに限定して使うのが賢明です。


9. 実際に得られた効果

開発体験の変化

項目 Before After
コードスタイル統一 PR指摘 → 修正 hooks で自動統一
セキュリティチェック 目視 サブエージェントが自動検出
Playwrightの定型処理 毎回ググる skills から自動参照
mainブランチ保護 「気をつける」 hooks で物理的にブロック

パイプラインの効果

  • 50並列スクレイピングで数百件のメッセージ取得が数十秒
  • メッセージ生成 → レビュー → 承認 のフローが半自動化
  • レビューサブエージェントの独立コンテキスト実行により品質が安定

10. ハマったポイントと解決策

Claude CLIの並列spawn時のファイルロック

問題: 20プロセス同時spawnすると ~/.claude.json のロック競合でエラー

解決:

// 1秒ずつスタガー起動
if (index > 0) {
  await new Promise((r) => setTimeout(r, 1000 * index));
}

CLIの出力からメッセージだけ取り出す

問題: Claude CLIの stdout にはログ、思考過程、メタ情報が混在

解決: マーカー方式でskill側に出力フォーマットを強制 + --output-format json で構造化出力を活用

---REPLY_START---
{ここだけ取る}
---REPLY_END---

Hooksのexit codeを間違えると何もブロックされない

問題: セキュリティhookで exit 1 を使っていたら、実は何もブロックされていなかった

解決: PreToolUseでブロックするには必ず exit 2 を使う。exit 1 は非ブロッキングエラーで操作が続行されてしまう。

# NG: exit 1 → 操作は続行される(ブロックされない!)
echo "Blocked!" >&2
exit 1

# OK: exit 2 → 操作がキャンセルされる
echo "BLOCKED: mainブランチへの直接操作は禁止です" >&2
exit 2

Hooksのmatcherでtool_inputの条件判定をしようとしてハマる

問題: matcherに tool == "Bash" && tool_input.command matches "..." のような式を書いたが動かない

解決: matcherはツール名の文字列マッチのみ。条件判定はhookスクリプト内でstdinのJSONをパースして行う。

// NG: matcherに条件式は書けない
"matcher": "tool == \"Bash\" && tool_input.command matches \"git push\""

// OK: matcherはツール名のみ、判定ロジックはスクリプト内
"matcher": "Bash"

エージェントの「嘘」防止

問題: AIが文脈に合わせて事実と異なる情報を生成することがある

解決: skillに「事実誤認禁止」ルールを明記 + レビューサブエージェントで二重チェック

## 絶対禁止:事実の捏造
- 確認できていない情報を断定しない
- 提供元データに存在しない内容を補完しない

さらに WebSearch ツールを許可し、「不明点は調べてから答える」を強制:

## 不明な情報への対応
- 不確実な情報はそのまま返さない
- WebSearchで裏取りしてから応答する(ハルシネーション防止)

まとめ:Claude Codeの「本当の使い方」

Claude Codeは単なるコード生成ツールではありません。

  • subagents で専門家を定義し、独立コンテキストで委譲する
  • skills でドメイン知識をモジュール化し、必要な時だけ読み込む
  • hooks で品質を確定的に担保する(exit 2 でブロック!)
  • rules でコーディング規約を常時適用する
  • --resume / --continue でセッション管理する
  • Agent Teams(実験的機能) でマルチエージェント協調する

これらを組み合わせると、AIは「質問に答えてくれるアシスタント」から**「チームの一員として実際にタスクを遂行するワーカー」**に変わります。

重要なのは**「AIに何をさせるか」ではなく「AIをどうシステムに組み込むか」**という設計の視点です。Claude Codeの拡張機構はその設計を支える強力な基盤になります。

ぜひ皆さんも、Claude Codeを「もう一人の開発者」として迎え入れてみてください。


参考

ディレクトリ構成(参考)

.claude/
├── agents/              # サブエージェント定義
│   ├── reply-generator.md
│   ├── reply-reviewer.md
│   └── code-reviewer.md
├── skills/              # スキル(ドメイン知識・ワークフロー)
│   ├── sender-profile/SKILL.md
│   ├── reply-rules/SKILL.md
│   ├── playwright-patterns/SKILL.md
│   └── agent-teams/SKILL.md
├── hooks/               # Hookスクリプト
│   ├── block-main-push.sh
│   ├── auto-format.sh
│   └── detect-console-log.sh
├── rules/               # 常時適用ルール
│   ├── coding-style.md
│   ├── development.md
│   └── agent-teams.md
├── settings.json        # Hooks設定・権限
└── settings.local.json  # ローカル設定(.gitignore推奨)
src/
├── auth/        # 認証・セッション管理
├── message/     # スクレイピング・生成・送信
├── scraper/     # Web巡回・データ収集
├── dashboard/   # Express + SSE監視画面
├── db/          # SQLiteデータ層
└── common/      # 共通設定・ユーティリティ
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?