記事一覧に戻る

CLAUDE.md も棚卸ししたい。prompt-audit で監査して、改善 PR まで自動化する

Claude Code の claude-api prompt-audit で古い指示を監査し、修正差分を GitHub Actions で Draft PR にする構成例。対象モデルの指定、変更範囲の検査、レビュー前の挙動比較まで紹介します。

2026年9月24日6 min read
Claude Code
GitHub Actions
Prompt Engineering
CI

この記事を共有

prompt-audit は修正差分まで出してくれる

Claude Code の claude-api スキルにある prompt-audit を紹介したい。

/claude-api prompt-audit

Anthropic の公式説明によると、アプリのプロンプトに加え、CLAUDE.md やスキルも監査できる。以前のモデル向けに書いた指示を、今使うモデルに照らして見直すためのコマンドだ。

おすすめしたいのは、指摘だけでなく監査レポートと修正差分が出るところ。どの指示をどう直すかが分かれば、そのままコードレビューに持ち込める。

そこで、監査と修正案の作成を GitHub Actions に任せ、Draft PR で受け取る構成を考えた。この記事の Workflow は実 API から PR 作成までの通し実行前の実装例。効果検証も今後実施予定で、品質・ツール呼び出し数・トークン数・所要時間を変更前後で比較する。仕様の確認日は 2026 年 9 月 24 日。

最初は対象を絞って試す

プロジェクトのルートで Claude Code を起動し、まずは次を実行する。

/claude-api prompt-audit CLAUDE.md と AGENTS.md を監査してください。対象モデルは、このプロジェクトで実際に使っているモデルです。モデル名と判断根拠をレポートの先頭に書き、監査結果と修正差分を提示してください。ファイルはまだ変更しないでください。

対象モデルが分かっているなら、その正式なモデル ID を明記するほうがよい。監査を実行するモデルと、指示文を使う対象モデルは別の概念で、両者が違う運用もある。

CLAUDE.md が @AGENTS.md のように別ファイルを参照している場合は、参照先も対象に含める。入口のファイルだけ見ても、実際の指示を監査できないためだ。

claude-api は Claude Code に同梱されている。コマンドが見つからなければ、Claude Code のバージョンとスキルの利用可否を確認する。CI が使用するバージョンも別途確認する。

公開されている監査手順では、指摘の場所・根拠・確信度をレポートに記載し、根拠が弱いものは差分に含めない。業務上の制約やプロジェクト固有の情報まで削るための機能ではない。

修正案を Draft PR で受け取る

通常は差分の提案までなので、CI では「指定ファイルの変更を作業ツリーに適用してよい」と明示する。その変更を検査し、create-pull-request で PR にする。

監査を毎週実行しても、変更がなければ新しい PR は不要。レポートをコミット対象から外し、指示文に差分があるときだけ PR を作る。

GitHub Actions の構成例

ここでは、追跡済みのルート CLAUDE.md と AGENTS.md だけを更新する。スキルまで広げる場合は、監査対象、変更検査、add-paths をまとめて変更する。監査対象に非公開の運用情報を含める場合は、レポート、差分 Artifact、PR 本文にもその内容が写りうるため、公開してよい情報だけを扱う。

導入前に、次を設定する。

  • Actions の Secret に ANTHROPIC_API_KEY を登録する。API 利用料が発生する。
  • Actions の Variable に CLAUDE_AUDIT_MODEL を登録する。利用可能な、固定したモデル ID を指定する。
  • リポジトリの Actions 設定で、ワークフローによる PR 作成を許可する。組織のポリシーによっては管理者の設定も必要になる。
  • 使用する Claude Code で claude-api と prompt-audit が利用できることを確認する。

以下を .github/workflows/prompt-audit.yml に置く。Claude Code Action はレビューしたコミットに固定した。この版は CLI 2.1.281 をインストールする。他の Action のメジャータグも、運用前に確認したコミット SHA へ固定しておく。

name: Prompt audit

on:
  workflow_dispatch:
  # Enable after a successful manual run:
  # schedule:
  #   - cron: '0 1 * * 1'

permissions:
  contents: read

concurrency:
  group: prompt-audit
  cancel-in-progress: false

jobs:
  audit:
    if: github.ref_name == github.event.repository.default_branch
    runs-on: ubuntu-latest
    timeout-minutes: 15
    outputs:
      sha: ${{ steps.baseline.outputs.sha }}
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.event.repository.default_branch }}
          fetch-depth: 0
          persist-credentials: false

      - name: Check prerequisites
        id: baseline
        env:
          AUDIT_MODEL: ${{ vars.CLAUDE_AUDIT_MODEL }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          python3 - <<'PY'
          import os, pathlib, re, subprocess
          if not re.fullmatch(r'claude-[a-z0-9][a-z0-9.-]*', os.environ['AUDIT_MODEL']):
              raise SystemExit('Set CLAUDE_AUDIT_MODEL to a full Claude model ID')
          if not os.environ['ANTHROPIC_API_KEY'].strip():
              raise SystemExit('Set ANTHROPIC_API_KEY')
          tracked = subprocess.check_output([
              'git', 'ls-files', '-z', '--', 'CLAUDE.md', 'AGENTS.md'
          ]).decode().split('\0')
          targets = [pathlib.Path(p) for p in tracked if p]
          if not targets or any(p.is_symlink() or not p.is_file() for p in targets):
              raise SystemExit('Expected at least one tracked regular target file')
          PY
          echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
          mkdir -p "$RUNNER_TEMP/prompt-audit"

      - name: Audit and edit the working tree
        id: audit
        # Reviewed v1 source; installs Claude Code 2.1.281.
        uses: anthropics/claude-code-action@8cf3482550831fb35a4fc3fbf7ca139cf8028b4c
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          github_token: ${{ github.token }}
          claude_args: >-
            --model ${{ vars.CLAUDE_AUDIT_MODEL }}
            --max-turns 40
            --max-budget-usd 3
            --tools "Skill,Read,Glob,Grep,Edit,Write,Bash"
            --allowedTools "Skill,Read,Glob,Grep,Edit,Write,Bash(git blame *),Bash(git log *)"
            --json-schema '{"type":"object","properties":{"status":{"type":"string","enum":["completed","blocked"]},"report":{"type":"string"}},"required":["status","report"],"additionalProperties":false}'
          prompt: |
            /claude-api prompt-audit
            Audit only tracked root CLAUDE.md and AGENTS.md, if present.
            Target model: ${{ vars.CLAUDE_AUDIT_MODEL }}.
            Apply high-confidence, in-scope text edits to the working tree.
            Preserve business constraints, security rules and project facts.
            Report medium/low-confidence findings without editing them.
            Do not create/delete files, stage, commit, push or open a PR.
            Return status=completed only if the skill ran and the audit finished.
            If the skill is unavailable or the audit cannot finish, return
            status=blocked with the reason; do not substitute a general rewrite.
            In report, include scope, target model, file:line, rationale,
            confidence, proposed diff and behavior still requiring verification.
            No justified findings means no edits, with status=completed.

      - name: Validate result and changes
        env:
          AUDIT_RESULT: ${{ steps.audit.outputs.structured_output }}
          AUDIT_CONCLUSION: ${{ steps.audit.outputs.conclusion }}
          AUDIT_BASE: ${{ steps.baseline.outputs.sha }}
        run: |
          python3 - <<'PY'
          import json, os, pathlib, subprocess
          def git(*args):
              return subprocess.check_output(['git', *args]).decode()
          result = json.loads(os.environ['AUDIT_RESULT'])
          report = result.get('report')
          if not isinstance(report, str) or not report.strip():
              raise SystemExit('Missing audit report')
          folder = pathlib.Path(os.environ['RUNNER_TEMP']) / 'prompt-audit'
          (folder / 'report.md').write_text(report, encoding='utf-8')
          if os.environ['AUDIT_CONCLUSION'] != 'success' or result.get('status') != 'completed':
              raise SystemExit('Audit did not complete; refusing PR creation')
          if len(report.encode('utf-8')) > 50000:
              raise SystemExit('Report too large for PR body; see artifact')
          base = os.environ['AUDIT_BASE']
          if git('rev-parse', 'HEAD').strip() != base:
              raise SystemExit('HEAD changed during audit')
          if git('diff', '--cached', '--name-only', base):
              raise SystemExit('Unexpected staged changes')
          changed = set(filter(None, git('diff', '--name-only', '-z', base).split('\0')))
          if changed - {'CLAUDE.md', 'AGENTS.md'}:
              raise SystemExit('Out-of-scope changes')
          if git('ls-files', '--others', '--exclude-standard'):
              raise SystemExit('Unexpected untracked files')
          if git('diff', '--diff-filter=D', '--name-only', base):
              raise SystemExit('Target file deleted')
          if git('diff', '--summary', base):
              raise SystemExit('File type or mode changed')
          git('diff', '--check', base)
          (folder / 'changes.patch').write_text(git('diff', base), encoding='utf-8')
          PY

      - name: Save report and diff
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: prompt-audit-report
          path: ${{ runner.temp }}/prompt-audit/
          if-no-files-found: warn
          retention-days: 14

  propose:
    needs: audit
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.event.repository.default_branch }}
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/download-artifact@v4
        with:
          name: prompt-audit-report
          path: ${{ runner.temp }}/prompt-audit/

      - name: Validate and apply audited diff
        env:
          AUDIT_BASE: ${{ needs.audit.outputs.sha }}
        run: |
          python3 - <<'PY'
          import os, pathlib, subprocess
          def git(*args):
              return subprocess.check_output(['git', *args]).decode()
          folder = pathlib.Path(os.environ['RUNNER_TEMP']) / 'prompt-audit'
          report = folder / 'report.md'
          patch = folder / 'changes.patch'
          if not report.is_file() or not report.read_text(encoding='utf-8').strip():
              raise SystemExit('Missing audit report')
          if report.stat().st_size > 50000 or not patch.is_file():
              raise SystemExit('Invalid audit artifact')
          base = os.environ['AUDIT_BASE']
          if git('rev-parse', 'HEAD').strip() != base:
              raise SystemExit('Default branch moved after the audit; rerun it')
          if git('status', '--porcelain'):
              raise SystemExit('Checkout is not clean')
          if patch.stat().st_size > 100000:
              raise SystemExit('Audit patch is too large')
          if patch.stat().st_size:
              subprocess.run(['git', 'apply', '--check', str(patch)], check=True)
              subprocess.run(['git', 'apply', str(patch)], check=True)
          changed = set(filter(None, git('diff', '--name-only', '-z', base).split('\0')))
          if changed - {'CLAUDE.md', 'AGENTS.md'}:
              raise SystemExit('Out-of-scope changes in audit patch')
          if git('diff', '--diff-filter=D', '--name-only', base):
              raise SystemExit('Target file deleted')
          if git('diff', '--summary', base):
              raise SystemExit('File type or mode changed')
          git('diff', '--check', base)
          if git('diff', '--cached', '--name-only') or git('ls-files', '--others', '--exclude-standard'):
              raise SystemExit('Unexpected staged or untracked changes')
          if git('diff', base).encode('utf-8') != patch.read_bytes():
              raise SystemExit('Transferred diff does not match the applied edits')
          PY

      - name: Create or update a draft PR
        uses: peter-evans/create-pull-request@v8
        with:
          token: ${{ github.token }}
          branch: chore/prompt-audit
          commit-message: 'docs: propose prompt audit improvements'
          title: 'docs: prompt-audit による指示文の改善案'
          body-path: ${{ runner.temp }}/prompt-audit/report.md
          draft: always-true
          add-paths: |
            CLAUDE.md
            AGENTS.md

claude_args は Claude Code Action の入力として渡している。CLI には非対話実行、ツール設定、ターン数・予算の指定がある。--allowedTools は確認なしで実行できるツールの設定で、利用できるツール集合は --tools で絞る。

この例は、監査を実行するモデルと監査対象のモデルを同じ ID にしている。ジョブは 15 分、モデルの処理は 40 ターンまでとし、API 利用には 3 米ドルの予算設定を渡す。予算到達を検出して停止する設定なので、請求額が厳密に 3 米ドル以下になる保証としては扱わない。

初回はデフォルトブランチを選んで手動実行し、スキルの呼び出し、レポート、編集範囲、PR 作成までを確認する。成功後に schedule のコメントを外すと、月曜日 01:00 UTC(日本時間 10:00)の週次実行になる。モデル変更時にも手動実行できる。

差分がないときは PR を作らない

レポートは構造化出力で受け取り、CI が一時ファイルに保存する。completed 以外なら PR 作成へ進めない。これは処理完了を判定するための仕組みで、監査内容の正しさを保証するものではない。

レポートと実際の差分は Artifact に残す。指示文を変更しなかった回もレポートは読めるが、レポート自体はコミットしない。

create-pull-request は作業ツリーに差分があるときに PR を作成し、固定したブランチ名で既存 PR を更新できる。差分がなくなると既存 PR が閉じられる場合もあるので、自動更新用のブランチに人手の修正を混在させない運用が分かりやすい。

draft: always-true を指定して、既存 PR に変更を追加した場合も Draft に戻す。PR 本文には監査レポートを載せる。監査ジョブはリポジトリの読み取り権限だけで実行し、レポートと差分を Artifact で PR 作成ジョブへ渡す。後者で開始時のコミットと差分を再検査し、書き込み権限を使って PR を作る。監査の途中で差分が作られても、検査前に書き込み権限で push はできない。

自動作成した PR の CI には注意する

この例は GITHUB_TOKEN で PR を作成する。GitHub の仕様上、このトークンが起こしたイベントでは、原則として後続のワークフローは起動しない。workflow_dispatch と repository_dispatch は例外になる。

つまり、PR ができたことと、PR 用のテストが走ったことは別だ。

必要な検証を同じワークフロー内で実行するか、権限を絞った GitHub App のトークンなどで PR を作る設計にする。上の例が確認するのは出力ファイルと変更範囲、差分の空白エラーまでで、プロンプトの振る舞いは検証していない。

また、これは管理下のデフォルトブランチを監査する例であり、外部 PR の内容をそのまま実行する用途には広げない。変更パスの検査は PR への混入を検出するもので、実行環境そのものを隔離する仕組みではない。監査結果の内容も人が確認する。

この CI の確認状況

Action の実装と照合し、引数の変換、構造化出力の受け渡し、ローカルの Git リポジトリを使った正常系・異常系を検証した。ジョブ間の差分受け渡しもローカルで模擬したが、GitHub Actions 上の接続部分は実行時に確認する必要がある。

Anthropic API を使った監査から GitHub 上の PR 作成までの通し実行は未確認。導入時には、認証、利用できるモデル、スキルの読み込み、変更あり・変更なし、既存 PR の更新を手動実行で確認する。API の実行に成功することと、指示文の改善効果があることは分けて検証する。

効果検証は今後実施予定

次は、変更前後の指示文で同じタスクを実行して比べる予定。対象は、小さなバグ修正・テスト追加・仕様が曖昧な依頼など、普段エージェントに任せる作業にする。

モデル ID、入力、開始時のリポジトリをそろえ、次を記録する。

観点比較する内容
成果物の品質要件を満たしたか、テストは通ったか、手直しが必要か
必要な確認指示を削ったことで、確認や必須テストが抜けないか
作業量ツール呼び出し数、入出力トークン数、所要時間

同じ条件で複数回実行し、成功率と値のばらつきも見る。削除した行数や 1 回の成功だけでは、改善したと判断しない。

まずは手元の CLAUDE.md に対して監査結果と差分を読んでみると、何を PR にしたいか判断しやすいと思う。

参考資料