Claude Media
Claude CodeでactをGitHub Actionsのpush前検証に使う手順

Claude CodeでactをGitHub Actionsのpush前検証に使う手順

actでworkflowをローカル実行し、終了コードとログをClaude Codeに返して直させる手順です。Stop hookの組み方と、actで再現できないOIDCやpermissionsの見分け方も扱います。

GitHub Actionsのworkflowは、pushしないと動かしてみられません。1行直してはpushし、赤くなっては直す。この往復を手元に引き寄せるのが、workflowをローカルのDockerコンテナで実行するツールのactです。

Claude Codeにworkflowを直させるなら、actの終了コードとログがそのまま検証ループになります。ただしactは、GitHub上の実行を完全には再現しません。この記事では、Stop hookでactを回して結果をClaude Codeへ返す構成と、act上の成功を信用してよい範囲を手順にします。

actで試せること、試せないこと

actは .github/workflows/ を読み、必要なDockerイメージを用意して、依存関係の順にジョブをコンテナで走らせます。環境変数とファイルシステムはGitHubに合わせて構成されます。コミットとpushを挟まずに、workflowの変更を確かめるための道具です。

手元で回す価値が高いのは、次のような失敗です。

  • run: のシェルスクリプトが途中で落ちる
  • ステップの順序や needs: の取り違え
  • matrixの組み合わせごとの挙動
  • ${{ }} 式が空文字になって、コマンドが壊れる

逆に、actのドキュメントが「未対応」と明記している機能があります。ここを知らずに使うと、ローカルでは緑なのにGitHubで赤くなります。

機能actでの扱い
permissions:(ジョブ単位)actでの扱い無視される
OpenID Connect(OIDC)の取得先actでの扱いURLが定義されない
concurrency: / run-name:actでの扱い無視される
environment: とデプロイ環境ごとのsecretsactでの扱い無視される・未対応
GITHUB_STEP_SUMMARY への書き込みactでの扱い破棄される
timeout-minutes / continue-on-error(ジョブ単位)actでの扱い無視される

以降の手順は、この表を前提に組みます。

準備:Docker、act、.actrc

actはDocker Engine APIに依存します。macOSならDocker Desktop、LinuxならDocker Engineが要ります。podmanなどの別バックエンドは、公式が「動くかもしれないが保証しない」としています。

インストールはHomebrewが手軽です。

brew install act
act -l

act -l は、実行対象のworkflowとジョブを一覧します。

既定のランナーイメージは、GitHubのホステッドランナーと同じ中身ではありません。ドキュメントも「意図的に不完全」と書いています。ubuntu-latest には、小さいほうから順にmicro・medium・largeの3段階のイメージが用意されています。GitHubのランナーに近い中身が要るときは、-P でフル版に差し替えます。

act -P ubuntu-latest=catthehacker/ubuntu:full-latest

毎回の引数は、リポジトリ直下の .actrc に1行1引数で書けます。Claude Codeに長いコマンドを組ませずに済みます。

--pull=false
--action-offline-mode
-e .github/act/event.json

--pull=false は、取得済みのイメージを使い回します。--action-offline-mode は、一度実行したactionをキャッシュから使います。どちらも、イメージやactionの取得にかかる待ちを減らすための設定です。

ローカル専用の目印をworkflowに入れる

ローカルで回したくないステップがあります。Claude Codeを呼ぶステップ、Slack通知、リリースのタグ付けなどです。actは実行中に環境変数 ACT を立てるので、ステップ単位なら次のように外せます。

- name: Claude Codeによるレビュー
  if: ${{ !env.ACT }}
  uses: anthropics/claude-code-action@v1

ジョブ単位の if: では env コンテキストが使えません。その場合は、イベントファイルに独自のプロパティを持たせます。.github/act/event.json を作ります。

{ "act": true }

ジョブ側は if: ${{ !github.event.act }} と書きます。イベントファイルを渡さない本番の実行では、この条件は常に真です。

claude-code-actionは、既定のGitHub App認証で id-token: write を要求します。OIDCの取得先がactでは定義されないので、このステップは外しておくのが前提です。ワークフロー全体の作り方はGitHub Actionsのワークフロー作成をClaude Codeで進める手順、Claude Code自身をworkflowに載せる側はClaude CodeをGitHub Actionsに組み込むにあります。

Stop hookでactの結果をClaude Codeへ返す

Claude Codeのhookは、コマンドの終了コードで振る舞いを変えます。Stop は「Claudeが応答を終えるとき」に発火し、終了コード2で抜けると標準エラーの内容がフィードバックとして渡され、Claudeは作業を続けます。

PostToolUseでファイルを保存するたびにactを走らせると、コンテナの起動が編集ごとに挟まります。actはイメージの用意からジョブの実行までを通すため、応答を終える直前に1回だけ回すStop hookが合います。静的検査のactionlintを編集ごとに回す構成はClaude Codeのactionlint活用にあり、両者は役割が違います。actionlintが書き間違いを落とし、actが実行時の失敗を落とします。

.claude/hooks/act-check.sh として保存します。

#!/bin/bash
INPUT=$(cat)
[ "$(echo "$INPUT" | jq -r '.stop_hook_active // false')" = "true" ] && exit 0
cd "$CLAUDE_PROJECT_DIR" || exit 0
 
# workflowに変更がなければ何もしない
git status --porcelain -- .github/workflows | grep -q . || exit 0
 
LOG=$(mktemp)
act push -W .github/workflows/ci.yml -j lint > "$LOG" 2>&1
CODE=$?
if [ "$CODE" -ne 0 ]; then
  echo "act が終了コード $CODE で失敗しました。ログ末尾:" >&2
  tail -n 40 "$LOG" >&2
  exit 2
fi

ポイントは4つです。

  • stop_hook_active が true なら即 exit 0。前回のhookを受けて続行中のターンで、同じ検査を延々と繰り返さない
  • workflowが未変更なら何もしない。ふだんの会話をactで遅くしない
  • ログ全体ではなく末尾40行だけを返す。Claude Codeのコンテキストを使い切らない
  • 対象は -W と -j で1つのジョブに絞る。全ジョブをローカルで回すと時間もディスクも嵩む

Stop hookは、連続で8回ブロックすると上書きされます。公式のトラブルシュートにある上限で、収束しないループは8回で打ち切られます。登録は次のとおりです。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/act-check.sh",
            "timeout": 300
          }
        ]
      }
    ]
  }
}

timeout は秒単位で、commandタイプの既定は10分です。ジョブの実測に合わせて短めに置くと、固まったコンテナを待ち続けずに済みます。実行権限も忘れずに付けます。

chmod +x .claude/hooks/act-check.sh

テストの自動実行にStopを使う考え方はClaude Code hooksでテストを自動実行すると共通です。

シークレットを渡すとき、Claude Codeに見せない

workflowが secrets.* や GITHUB_TOKEN を使うなら、actに渡す必要があります。GITHUB_TOKEN はGitHubが自動で用意するものなので、ローカルでは自前で渡します。GitHub CLIが入っていれば、次の形が公式に示されています。

act -s GITHUB_TOKEN="$(gh auth token)"

この書き方は、シェルの履歴にトークンが残ります。引数に値を書かず -s MY_SECRET とだけ書くと、同名の環境変数を使い、無ければ入力を促されます。ファイルで渡す --secret-file my.secrets は、.env と同じ書式です。

ファイルで渡す場合は、Claude Codeからの読み取りを permissions で拒否しておきます。actの実行だけは許可します。

{
  "permissions": {
    "allow": ["Bash(act *)"],
    "deny": ["Read(./.secrets)", "Read(./.env)"]
  }
}

.secrets も .gitignore に入れておきます。Stop hookの中でシークレットを組み立てるときも、ログに値を出さない書き方にします。

CLAUDE.mdで直し方の作法を縛る

hookが返すのはログです。ログを受けて何をするかは、CLAUDE.mdで決めます。

## GitHub Actions
- workflowを編集したら、応答を終える前にactの結果が返る。失敗したらworkflow側を直す
- `if: ${{ !env.ACT }}` を足して失敗するステップを外さない。外してよいのは、人が承認した外部送信のステップだけ
- actが対応していない機能(OIDC、permissions、concurrency、environment)の挙動は、actの成功を根拠にしない。変更点を人に伝える
- 失敗の原因がactの環境差だと判断したら、直さずに理由を報告する

2行目が肝です。赤を消す最短の手は、失敗するステップを !env.ACT で外すことです。これを禁じておかないと、検証ループは常に緑になります。3行目は、次の節の話につながります。

失敗がactの環境差かを見分ける

actが赤いとき、直すべきなのはworkflowなのか、actの環境なのか。ログを受けたClaude Codeにこの切り分けを任せる前に、人が基準を持っておくと迷いません。

ログの症状疑う場所対応
command not found(jq やビルドツール)疑う場所既定イメージに入っていない対応-P でフル版イメージに替えるか、workflowにインストール手順を足す
トークンやsecretsが空で認証エラー疑う場所-s を渡していない対応act -s で渡す
upload-artifact が動かない疑う場所アーティファクトサーバー未起動対応--artifact-server-path $PWD/.artifacts を付ける
${{ github.event.pull_request... }} が空疑う場所イベントのペイロードが無い対応-e でイベントファイルを渡す
matrixの全組み合わせが走って遅い疑う場所絞り込み未指定対応--matrix node:8 のように絞る
OIDCトークンの取得で失敗疑う場所act未対応対応ローカルでは外し、GitHub上で確かめる
run: の途中で落ちる疑う場所workflowの誤り対応Claude Codeにその行を直させる

最後の行が、act本来の使いどころです。run: のスクリプトが落ちる失敗は、環境差を排したあとに残る失敗として、直す価値が高いものです。

actが緑でも、pushで赤くなるケース

actの成功は「run: が動いた」ことの保証であり、「workflowがGitHub上で動く」ことの保証ではありません。とくに次の3つは、act側で通ってしまう側に倒れます。

permissionsの不足。ジョブ単位の permissions: は無視されるため、pull-requests: write の付け忘れはactでは検出されません。GitHub上では403になります。claude-code-actionのように id-token: write を要求するactionでは、取りこぼしが致命的です。

OIDCを使うデプロイ。クラウドへのキーレス認証は、取得先のURLがactに無いので再現できません。仕組みの側はWIFをGitHub Actionsと連携するで扱っています。

環境ごとのsecretsとconcurrency。 environment: に紐づくsecretsや承認ルールは、actでは意味を持ちません。同時実行の制御を決める concurrency: も無視されます。

この3つは、Claude Codeが「actは緑でした」と報告しても、読む側が割り引くところです。CLAUDE.mdの3行目に書いた「根拠にしない」は、この意味です。

手順の全体像

手順

push前の検証ループ

  1. 1

    workflowを編集する

    Claude Codeが .github/workflows/ を直します。

  2. 2

    Stop hookがactを回す

    変更があるときだけ、対象の1ジョブを実行します。

  3. 3

    失敗ログの末尾を返す

    終了コード2で、ログの最後の40行がClaude Codeに渡ります。

  4. 4

    環境差か、workflowの誤りかを切り分ける

    環境差は報告、誤りは修正して再実行します。

  5. 5

    pushしてGitHub上で残りを確かめる

    permissions、OIDC、environmentはここで初めて検証されます。

まとめ

actで先に落とせるのは、スクリプトとデータの流れの誤りです。権限と認証の誤りは、pushするまで残ります。Stop hookに組み込むなら、対象を1ジョブに絞り、!env.ACT で失敗を隠すことを禁じるところまでをセットにすると、緑の意味が崩れません。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/hooks-guide
  2. code.claude.com/docs/en/github-actions
  3. github.com/nektos/act
  4. nektosact.com/usage/index.html
  5. nektosact.com/not_supported.html
  6. nektosact.com/usage/runners.html
  7. docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
この記事を共有:XはてブLinkedIn