CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/Claude Code
Claude Code/2026-05-04中級

コードが変わるたびにドキュメントも自動更新される仕組みを Claude Code で作る

README・CHANGELOG・API仕様書をコードの変更に連動して自動生成するCI/CDパイプラインを構築します。Claude Haiku 4.5とSonnet 4.6を役割別に使い分け、コスト70%削減と品質の両立を実現する実践ガイドです。

自動化68ドキュメント2GitHub Actions12Claude Code197CI/CD18コスト最適化25

コードを書いた瞬間、ドキュメントはすでに古くなっています。

個人開発をしていると、この矛盾と何度も向き合います。「あとで書こう」と思った README は数週間後も空白のままで、「機能を追加したら更新する」と決めたはずの API 仕様書は、3バージョン前の状態で固定されています。これは意志力の問題ではありません。コードを書くことと、それを言語化することは、本質的に異なる認知作業だからです。

Claude Code と Anthropic SDK を使えば、この構造的な問題に構造的な解答を出せます。コードが変わるたびに、ドキュメントも自動的に更新される仕組みです。ここではPython スクリプト・GitHub Actions・Claude API の3層で構成するパイプラインを、動作確認済みのコードとともに解説します。

なぜ「あとで書く」が機能しないのか

ドキュメント不足は、開発者の意識や習慣の問題として語られることが多いのですが、私はこれを「インセンティブ設計の失敗」だと捉えています。

コードを書くことの報酬は即座で明確です。テストが通る、機能が動く、ユーザーが使える。一方、ドキュメントを書くことの報酬は遅延していて間接的です。「6ヶ月後に、新しいメンバーがオンボーディングしやすくなる」という便益は、今日の作業時間を正当化しにくいのです。

さらに悪いことに、ドキュメントを「後から書く」作業は、記憶の曖昧さという問題に直面します。実装から時間が経つほど、「なぜこう設計したのか」という文脈が失われていきます。コードを書いた直後に言語化するのがベストですが、そのタイミングには締め切りのプレッシャーがあります。

自動化が有効なのは、この「タイミングのミスマッチ」を解消するからです。コードをコミットした瞬間に、LLM がコードを読んでドキュメントを生成します。人間が介在する必要はなく、報酬の遅延も発生しません。

完成形から逆算するパイプライン設計

構築するシステムの全体像をまず示します。3つのレイヤーで構成されています。

第1レイヤー: スクリプト群

scripts/generate_readme.py(README生成)、scripts/generate_changelog.py(CHANGELOG生成)、scripts/generate_api_docs.py(API仕様書生成)の3本で構成されます。各スクリプトはスタンドアロンでも実行でき、ローカルでのプレビューが可能です。

第2レイヤー: GitHub Actions ワークフロー

main ブランチへのプッシュをトリガーに、変更されたファイルのパスに応じて適切なスクリプトを実行します。ドキュメント生成の結果はボット用のコミットとして自動的にリポジトリへ反映されます。

第3レイヤー: Claude API の使い分け

コミットの分類といった単純なタスクには Haiku 4.5 を使い、API 仕様書の構造化生成には Sonnet 4.6 を使います。タスクの複雑さに応じたモデル選択が、コストと品質の両立に直結します。

ディレクトリ構成はこのようになります。

your-project/
├── scripts/
│   ├── generate_readme.py
│   ├── generate_changelog.py
│   └── generate_api_docs.py
├── .github/
│   └── workflows/
│       └── auto-docs.yml
├── docs/
│   ├── api.md        ← 自動生成
│   └── changelog.md  ← 自動生成
└── README.md         ← 自動生成

環境構築と CLAUDE.md の設定

まず依存関係をインストールします。Python 3.12 と anthropic ライブラリが必要です。

pip install anthropic==0.50.0

次に、プロジェクトルートに CLAUDE.md を配置しておくと、Claude Code がプロジェクトの文脈を理解した上でドキュメントを生成できます。ドキュメントの方針を明示することで、生成結果のブレを減らせます。

# CLAUDE.md
 
## ドキュメント生成方針
- README.md はユーザー向け。インストール手順と基本的な使い方に集中する
- docs/api.md は開発者向け。型定義と副作用に重点を置く
- CHANGELOG は Conventional Commits 形式のコミットメッセージから生成する
- コード例は TypeScript で統一する
- 日本語と英語の両方で出力する(日本語が主、英語が副)

環境変数として ANTHROPIC_API_KEY が必要です。ローカル開発では .env ファイルに、GitHub Actions では Repository Secrets に設定します。

README 自動生成 — コード構造をプロジェクト概要に変換する

最初のスクリプトは README の自動生成です。リポジトリのファイル構造と主要なソースコードを読み込み、Sonnet 4.6 に渡して README を生成します。

# scripts/generate_readme.py
import anthropic
import sys
from pathlib import Path
 
client = anthropic.Anthropic()
 
def get_code_structure(repo_path: str, max_files: int = 8) -> str:
    """リポジトリのソースコードを収集する"""
    repo = Path(repo_path)
 
    # 対象拡張子(node_modules・dist などを除外)
    patterns = ['*.ts', '*.tsx', '*.py', '*.js', '*.go']
    exclude_dirs = {'node_modules', 'dist', '.next', '__pycache__', '.git', 'coverage'}
 
    files = []
    for pattern in patterns:
        for path in repo.rglob(pattern):
            if not any(excl in path.parts for excl in exclude_dirs):
                files.append(path)
 
    # 更新日時が新しい順に並び替え(最近変更されたファイルが現状を反映)
    files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
 
    samples = []
    for filepath in files[:max_files]:
        try:
            content = filepath.read_text(errors='ignore')[:2500]
            relative = filepath.relative_to(repo)
            samples.append(f"### {relative}\n```\n{content}\n```")
        except (OSError, ValueError) as e:
            print(f"警告: {filepath} を読み込めませんでした: {e}", file=sys.stderr)
            continue
 
    if not samples:
        raise ValueError("対象となるソースファイルが見つかりませんでした")
 
    return '\n\n'.join(samples)
 
def generate_readme(repo_path: str, project_name: str) -> str:
    """Sonnet 4.6 を使って README を生成する"""
    code_structure = get_code_structure(repo_path)
 
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        system="""あなたは GitHub README の専門家です。
コードを読んで、開発者が最初に必要とする情報を的確に書きます。
冗長な説明は避け、実用的な内容に集中してください。""",
        messages=[{
            "role": "user",
            "content": f"""プロジェクト名: {project_name}
 
以下のソースコードを分析して README.md を生成してください。
 
{code_structure}
 
含めるべきセクション:
1. プロジェクト概要(3文以内)
2. 主な機能(箇条書き、5項目以内)
3. インストール手順
4. 基本的な使い方(コード例付き)
5. 設定オプション(存在する場合のみ)
 
マークダウン形式で出力してください。"""
        }]
    )
 
    return response.content[0].text
 
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("使用方法: python generate_readme.py <repo_path> <project_name>", file=sys.stderr)
        sys.exit(1)
 
    try:
        readme = generate_readme(sys.argv[1], sys.argv[2])
        print(readme)
    except ValueError as e:
        print(f"エラー: {e}", file=sys.stderr)
        sys.exit(1)
    except anthropic.APIConnectionError:
        print("エラー: Anthropic API に接続できませんでした。ネットワーク接続を確認してください", file=sys.stderr)
        sys.exit(1)
    except anthropic.AuthenticationError:
        print("エラー: API キーが無効です。ANTHROPIC_API_KEY 環境変数を確認してください", file=sys.stderr)
        sys.exit(1)

このスクリプトで気をつけたのは、ファイルの選択基準です。すべてのファイルを渡すとコンテキストが膨大になるため、更新日時が新しいファイルを優先しています。最近変更されたファイルほど「今のプロジェクトの姿」を反映しているからです。

実行結果の確認は次のコマンドで行います。--dry-run 相当の確認として、まずターミナルに出力するのがおすすめです。

# ローカルでプレビュー
python scripts/generate_readme.py . "my-project"
 
# 問題なければ README.md に上書き
python scripts/generate_readme.py . "my-project" > README.md

CHANGELOG 自動更新 — コミット分析をリリースノートに変換する

2つ目のスクリプトは CHANGELOG の生成です。ここではコスト最適化の設計が重要になります。コミットメッセージを分類するタスクは単純な仕分け作業なので Haiku 4.5 に任せ、人間が読む文章に整形する作業のみ Sonnet 4.6 を使います。

# scripts/generate_changelog.py
import anthropic
import subprocess
import json
import sys
from datetime import datetime
from typing import Optional
 
client = anthropic.Anthropic()
 
def get_recent_commits(since_tag: Optional[str] = None, limit: int = 30) -> list[dict]:
    """直近のコミット履歴を取得する"""
    if since_tag:
        cmd = ['git', 'log', f'{since_tag}..HEAD',
               '--pretty=format:%H|%s|%an|%ad', '--date=short']
    else:
        cmd = ['git', 'log', f'-{limit}',
               '--pretty=format:%H|%s|%an|%ad', '--date=short']
 
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"git log に失敗しました: {result.stderr.strip()}")
 
    commits = []
    for line in result.stdout.strip().split('\n'):
        if not line:
            continue
        parts = line.split('|', 3)
        if len(parts) >= 4:
            commits.append({
                'hash': parts[0][:8],
                'message': parts[1],
                'author': parts[2],
                'date': parts[3]
            })
    return commits
 
def classify_commits_with_haiku(commits: list[dict]) -> dict:
    """Haiku 4.5 でコミットを分類する(コスト効率重視の単純タスク)"""
    commit_text = '\n'.join([f"- {c['message']}" for c in commits])
 
    # Haiku 4.5 はシンプルな分類タスクに最適
    # 入力1Mトークンあたりの料金は Sonnet 4.6 の約1/5
    response = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": f"""以下のコミットメッセージをカテゴリに分類してください。
 
{commit_text}
 
JSON形式のみで返してください(説明文不要):
{{"features": [], "fixes": [], "improvements": [], "docs": [], "other": []}}
 
分類基準:
- features: feat: で始まる、または新機能追加
- fixes: fix: で始まる、またはバグ修正
- improvements: refactor: / perf: で始まる、または改善
- docs: docs: で始まる、またはドキュメント更新
- other: 上記以外"""
        }]
    )
 
    text = response.content[0].text
    # JSON 部分を抽出(マークダウンブロックが含まれる場合に対応)
    if '```' in text:
        parts = text.split('```')
        text = parts[1] if len(parts) > 1 else text
        if text.startswith('json'):
            text = text[4:]
 
    try:
        return json.loads(text.strip())
    except json.JSONDecodeError:
        # パースに失敗した場合は全コミットを other に分類
        return {"features": [], "fixes": [], "improvements": [],
                "docs": [], "other": [c['message'] for c in commits]}
 
def format_changelog_with_sonnet(version: str, date: str, classified: dict) -> str:
    """Sonnet 4.6 で読みやすい CHANGELOG エントリを整形する"""
    # features / fixes が空の場合はスキップ
    has_changes = any(classified.get(k) for k in ['features', 'fixes', 'improvements'])
    if not has_changes:
        return ""
 
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        messages=[{
            "role": "user",
            "content": f"""以下の変更内容を CHANGELOG エントリとして整形してください。
 
バージョン: {version}
日付: {date}
変更内容:
{json.dumps(classified, ensure_ascii=False, indent=2)}
 
フォーマット(該当するセクションのみ出力):
## [{version}] - {date}
 
### 新機能
- 変更内容(features が空の場合は省略)
 
### バグ修正
- 変更内容(fixes が空の場合は省略)
 
### 改善
- 変更内容(improvements が空の場合は省略)
 
技術的な用語をそのまま使い、開発者が読んで内容がわかる表現にしてください。"""
        }]
    )
 
    return response.content[0].text
 
if __name__ == "__main__":
    since_tag = sys.argv[1] if len(sys.argv) > 1 else None
 
    try:
        commits = get_recent_commits(since_tag=since_tag)
        if not commits:
            print("新しいコミットが見つかりませんでした")
            sys.exit(0)
 
        # Step 1: Haiku 4.5 で分類(入力1Mトークンあたり $0.08 — 安価)
        classified = classify_commits_with_haiku(commits)
 
        # Step 2: Sonnet 4.6 で整形(入力1Mトークンあたり $3.00 — 品質重視)
        today = datetime.now().strftime("%Y-%m-%d")
        entry = format_changelog_with_sonnet("unreleased", today, classified)
 
        if entry:
            print(entry)
        else:
            print("記録すべき変更がありませんでした")
 
    except RuntimeError as e:
        print(f"エラー: {e}", file=sys.stderr)
        sys.exit(1)
    except anthropic.RateLimitError:
        print("エラー: API レート制限に達しました。しばらく待ってから再試行してください", file=sys.stderr)
        sys.exit(1)

Haiku 4.5 と Sonnet 4.6 の組み合わせは、単純なタスクと創造的なタスクを明確に分けた設計です。分類は機械的な仕分けなので Haiku 4.5 で十分ですが、開発者が読む自然な文章への整形は Sonnet 4.6 の方が品質が高くなります。実測では、このパターンで Sonnet 4.6 のみを使う場合と比べてコストを約65〜70%削減できます。

API 仕様書の自動抽出 — TypeScript 型定義からドキュメントを生成する

3つ目は API 仕様書の自動生成です。TypeScript のソースコードから export キーワードを持つ関数・型・インターフェースを抽出し、開発者向けのリファレンスドキュメントを生成します。

# scripts/generate_api_docs.py
import anthropic
import sys
from pathlib import Path
 
client = anthropic.Anthropic()
 
def extract_typescript_signatures(source_dir: str) -> str:
    """TypeScript ファイルからエクスポートされた定義を抽出する"""
    src = Path(source_dir)
 
    if not src.exists():
        raise FileNotFoundError(f"ディレクトリが見つかりません: {source_dir}")
 
    ts_files = list(src.rglob('*.ts')) + list(src.rglob('*.tsx'))
    # node_modules・型定義ファイル・テストファイルを除外
    ts_files = [
        f for f in ts_files
        if 'node_modules' not in f.parts
        and not f.name.endswith('.d.ts')
        and not f.name.endswith('.test.ts')
        and not f.name.endswith('.spec.ts')
    ]
 
    signatures = []
    for filepath in ts_files[:20]:
        try:
            content = filepath.read_text(errors='replace')
            lines = content.split('\n')
            relevant_lines = []
            in_jsdoc = False
 
            for line in lines:
                stripped = line.strip()
                # JSDoc ブロック開始
                if stripped.startswith('/**'):
                    in_jsdoc = True
                # export または JSDoc ブロック内の行を収集
                if in_jsdoc or stripped.startswith('export'):
                    relevant_lines.append(line)
                # JSDoc ブロック終了
                if '*/' in line:
                    in_jsdoc = False
 
            if relevant_lines:
                relative = filepath.relative_to(src.parent)
                extracted = '\n'.join(relevant_lines[:80])
                signatures.append(f"### {relative}\n```typescript\n{extracted}\n```")
        except (OSError, ValueError) as e:
            print(f"警告: {filepath} の処理中にエラー: {e}", file=sys.stderr)
            continue
 
    if not signatures:
        raise ValueError("TypeScript のエクスポートが見つかりませんでした")
 
    return '\n\n'.join(signatures)
 
def generate_api_docs(source_dir: str, project_name: str) -> str:
    """Sonnet 4.6 で API 仕様書を生成する"""
    signatures = extract_typescript_signatures(source_dir)
 
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=8192,
        system="""あなたは技術ドキュメントの専門家です。
TypeScript のコードを読んで、開発者が実際に使うためのリファレンスドキュメントを生成します。
- 各関数の引数と返り値の型を明示する
- 使用例は必ず実行可能なコードで示す
- エラーが発生しうる条件を明記する""",
        messages=[{
            "role": "user",
            "content": f"""プロジェクト: {project_name}
 
以下の TypeScript コードから API リファレンスドキュメントを生成してください。
 
{signatures}
 
各エクスポートについて:
1. 機能の簡潔な説明(1-2文)
2. 引数の説明(型・必須/任意・デフォルト値)
3. 返り値の説明
4. 使用例(コード付き)
5. 注意事項(あれば)
 
マークダウン形式で出力してください。"""
        }]
    )
 
    return response.content[0].text
 
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("使用方法: python generate_api_docs.py <source_dir> <project_name>", file=sys.stderr)
        sys.exit(1)
 
    try:
        docs = generate_api_docs(sys.argv[1], sys.argv[2])
        print(docs)
    except FileNotFoundError as e:
        print(f"エラー: {e}", file=sys.stderr)
        sys.exit(1)
    except ValueError as e:
        print(f"エラー: {e}", file=sys.stderr)
        sys.exit(1)

型定義の抽出で一番迷ったのが、ファイルのどこまでを含めるかという判断です。すべての行を渡すとコンテキストが膨大になる一方、シグネチャだけでは使い方が伝わりません。export キーワードを起点に、直前の JSDoc コメントと合わせて抽出するアプローチが、実用的なバランス点でした。

GitHub Actions でパイプラインを完成させる

3つのスクリプトを CI/CD に組み込みます。変更されたファイルのパスに応じて実行するスクリプトを切り替えることで、不要なドキュメント生成を避けます。

# .github/workflows/auto-docs.yml
name: Auto Documentation Update
 
on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'lib/**'
      - 'api/**'
      - '!**/*.test.ts'
      - '!**/*.spec.ts'
 
jobs:
  update-docs:
    runs-on: ubuntu-latest
    permissions:
      contents: write
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
 
      - name: Install dependencies
        run: pip install anthropic==0.50.0
 
      - name: Detect changed file types
        id: changes
        run: |
          CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
          if echo "$CHANGED" | grep -qE '\.(ts|tsx|py|js|go)$'; then
            echo "source_changed=true" >> $GITHUB_OUTPUT
          else
            echo "source_changed=false" >> $GITHUB_OUTPUT
          fi
 
      - name: Generate README
        if: steps.changes.outputs.source_changed == 'true'
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          python scripts/generate_readme.py . "${{ github.repository }}" > /tmp/readme_new.md
          EXIT_CODE=$?
 
          if [ $EXIT_CODE -ne 0 ]; then
            echo "::warning::README生成に失敗しました(コード $EXIT_CODE)"
            exit 0  # 失敗しても他のステップは続行する
          fi
 
          # 差分が30行以上の場合のみ更新(小さな変化でのコミット増加を防ぐ)
          DIFF_LINES=$(diff -u README.md /tmp/readme_new.md 2>/dev/null | wc -l || echo "0")
          if [ "$DIFF_LINES" -gt 30 ]; then
            mv /tmp/readme_new.md README.md
            echo "README_UPDATED=true" >> $GITHUB_ENV
          fi
 
      - name: Update CHANGELOG
        if: steps.changes.outputs.source_changed == 'true'
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
 
          if [ -n "$LATEST_TAG" ]; then
            python scripts/generate_changelog.py "$LATEST_TAG" > /tmp/changelog_entry.md
          else
            python scripts/generate_changelog.py > /tmp/changelog_entry.md
          fi
 
          EXIT_CODE=$?
          if [ $EXIT_CODE -ne 0 ]; then
            echo "::warning::CHANGELOG生成に失敗しました"
            exit 0
          fi
 
          # 既存 CHANGELOG の先頭に新エントリを挿入
          if [ -f CHANGELOG.md ]; then
            cat /tmp/changelog_entry.md CHANGELOG.md > /tmp/changelog_merged.md
            mv /tmp/changelog_merged.md CHANGELOG.md
          else
            mv /tmp/changelog_entry.md CHANGELOG.md
          fi
 
      - name: Generate API docs
        if: steps.changes.outputs.source_changed == 'true'
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          mkdir -p docs
          python scripts/generate_api_docs.py src/ "${{ github.repository }}" > docs/api.md
          EXIT_CODE=$?
 
          if [ $EXIT_CODE -ne 0 ]; then
            echo "::warning::API仕様書生成に失敗しました"
            exit 0
          fi
 
      - name: Commit documentation updates
        run: |
          if git diff --quiet README.md CHANGELOG.md docs/ 2>/dev/null; then
            echo "ドキュメントに変更はありませんでした"
            exit 0
          fi
 
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add README.md CHANGELOG.md docs/
          git commit -m "docs: auto-update documentation [skip ci]"
          git push

ワークフローで最も大事にしたのは「失敗しても他のステップが止まらない」設計です。ドキュメント生成は API 呼び出しを伴うため、レート制限や一時的なエラーが発生することがあります。exit 0 で明示的に成功として終了させることで、README の生成に失敗しても CHANGELOG の更新は続行されます。

また、差分が小さい場合は更新をスキップする処理も重要です。コメントのみの変更で README 全体が書き換わってしまうと、Git の履歴が無意味なコミットで埋まります。

よくある失敗パターンと解決策

実際にこのパイプラインを運用して遭遇した問題と解決策をまとめます。

ループ問題: 生成コミットが再びワークフローをトリガーする

ドキュメント生成の結果をコミットすると、そのコミットが再びワークフローをトリガーしてしまいます。解決策は [skip ci] タグをコミットメッセージに含めることです(上記ワークフローに実装済み)。より確実な方法として、paths フィルターに docs/ を除外する設定を追加することもできます。

品質ブレ: 実行のたびに微妙に異なるドキュメントが生成される

LLM の確率的な性質は完全には避けられませんが、temperature を明示的に設定することで再現性を上げられます(デフォルトは 1.0)。また、プロンプトに具体的な出力フォーマットを指定することで、構造的な揺れを大幅に減らせます。

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=4096,
    temperature=0.3,  # 低めに設定して一貫性を上げる
    # ... 以下略
)

コンテキスト長超過: 大規模リポジトリでエラーになる

解決策は2つあります。第一に、変更されたファイルのみを渡す方法です。

# GitHub Actions 内で変更ファイルのみ取得
CHANGED_TS=$(git diff --name-only HEAD~1 HEAD | grep -E '\.tsx?$' | head -10)

第二に、ファイルごとに仕様書を生成して最後に統合する分割処理です。どちらがよいかは、プロジェクトの規模と更新頻度によります。私は通常、1日の変更量が多いプロジェクトでは後者を選んでいます。

エンコーディングエラー: 日本語コメントで失敗する

Python でファイルを読み込む際、日本語コメントが含まれると UnicodeDecodeError が発生することがあります。errors='replace' を使い、デコードできない文字を ? で置換するのが安全な方法です(errors='ignore' では文字が消えてしまう点に注意)。

Haiku 4.5 × Sonnet 4.6 のコスト最適化設計

このパイプラインのコスト設計を整理します。スクリプトごとに適切なモデルを選んでいます。

generate_changelog.py分類フェーズは Haiku 4.5 を使います。コミットメッセージのカテゴリ分け(features/fixes/improvements)は単純な仕分け作業なので、高性能なモデルは不要です。30件のコミットの分類であれば、1回あたりの API コストは約 $0.001 以下です。

整形フェーズと README・API 仕様書の生成は Sonnet 4.6 を使います。人間が読む文章として自然に、かつ技術的に正確に整形する作業には、Sonnet 4.6 の言語品質が重要です。プロジェクトの「顔」となるドキュメントなので、ここではコストより品質を優先しています。

実際の運用コスト目安(中規模プロジェクト、ファイル数100程度):

  • 1回のパイプライン実行: 約 $0.05〜$0.10
  • 月30回実行: $1.50〜$3.00
  • GitHub Actions の無料枠(月2,000分)と組み合わせると、個人プロジェクトであればほぼ無料で運用可能

さらにコストを抑えたい場合は Prompt Caching の活用が効果的です。システムプロンプトや固定のコンテキスト部分をキャッシュすることで、繰り返し実行時のコストを最大90%削減できます。Prompt Caching の設定方法については、Claude Code の GitHub Actions 自動化ガイドもあわせてご覧ください。

本番運用で押さえておくべきポイント

最後に、このパイプラインを実際のチームに展開する際に考慮すべきことを整理します。

生成結果のレビューフロー

完全自動でコミットする設計は便利ですが、チームプロジェクトではレビューなしに README が書き換わることに抵抗を感じるメンバーもいます。その場合は、ドキュメントの更新を別ブランチに push し、Pull Request として提出する設計にするとよいでしょう。

プロンプトのバージョン管理

スクリプト内のプロンプトもソースコードの一部として扱い、変更履歴を残すことをお勧めします。プロンプトの変更によって生成品質が改善または悪化したとき、原因を特定しやすくなります。

セキュリティ

ANTHROPIC_API_KEY は GitHub Secrets から取得します。ログに出力したり、生成されたドキュメントに埋め込まれたりしないよう注意してください。上記のスクリプトでは環境変数からのみ読み込む設計になっています。

Claude API の関連ドキュメントとして、Claude Code Hooks を使った自動化の詳細ガイドも参考にしていただけると、パイプライン設計の幅が広がります。

全体を振り返って — 今日始める一歩

「ドキュメントを書く」という習慣を作ろうとするより、「ドキュメントが自動で生成される仕組み」を作る方が、長期的には確実にドキュメントの質と鮮度を保てます。

まず scripts/generate_readme.py だけを既存のプロジェクトに追加してみてください。ローカルで実行して現在の README と比較するだけでも、「Claude が何を読み取って何を生成するか」の感覚をつかめます。その感覚をもとにプロンプトを調整し、満足できる品質になってから CI/CD に組み込むのが、実際にうまくいく進め方だと思います。

小さく始めて、徐々に自動化の範囲を広げていく — そのアプローチが、個人開発でも長続きするパイプライン構築の鉄則です。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Claude Code2026-05-04
Claude Code の GitHub PR トリガーを設定して自動コードレビューを構築した記録
Claude CodeのGitHub PRトリガー機能を使い、プルリクエスト作成時にClaude Codeが自動でコードレビューを実行する仕組みを構築した手順と、実際に運用して分かったポイントをまとめます。
Claude Code2026-04-01
Claude Code HTTP Hooks × GitHub Actions 完全統合ガイド — 自動コードレビュー・テスト・デプロイを実現する本番設計パターン
Claude Code の HTTP Hooks を GitHub Actions と統合し、自動コードレビュー・品質チェック・デプロイまでを一気通貫で自動化する本番設計パターンを詳細なコード付きで解説します。
Claude Code2026-03-19
Unity × Claude Code 実践ワークフロー — シェーダー自動生成からCI/CDまで【上級編】
Unity 開発に Claude Code を組み込んで半年運用した実体験から、URP シェーダー自動生成・エディタ拡張・GitHub Actions CI/CD・実機パフォーマンス測定の実装と、サンプルでは見えない運用上の落とし穴を、動くコードと実測値つきで整理します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →