はじめに
Claude Codeはローカルのターミナルで動く対話型のAIエージェントです。公式でGitHub Actions用のアクション(claude-code-action)が提供されていて、Issueのコメントやラベル付与をトリガーにClaude Codeを自動実行できます。
ドキュメント通りにセットアップすれば基本的な動作はすぐできます。ただ、実際に使い始めると「ローカルでは動くのに」「設定が反映されているのかわからない」という場面がいくつかありました。
本記事では、Claude Code × GitHub Actionsで自律的なエージェントを作る中でハマったポイントと、その対処法を書きます。
基本的なセットアップ
一番簡単なのは、Claude Codeで /install-github-app を実行する方法です。GitHub Appのインストール、Secretsの設定、ワークフローファイルの配置まで、対話的にガイドしてくれます。
手動でやる場合は、以下の手順になります。
- Claude GitHub App をリポジトリにインストールする
- リポジトリのSecretsに認証情報を追加する
- ワークフローファイルを
.github/workflows/に配置する
認証方法は2つあります。Anthropic APIのAPIキーを使う方法と、Claude Pro/Maxサブスクリプションに紐づいたOAuthトークンを使う方法です。
APIキーの場合はリポジトリのSecretsに ANTHROPIC_API_KEY を登録します。OAuthトークンの場合はローカルで claude setup-token を実行してトークンを生成し、CLAUDE_CODE_OAUTH_TOKEN として登録します。
最小限のワークフローはこんな形です。
name: Claude Code Agent
on:
issue_comment:
types: [created]
jobs:
respond:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- uses: anthropics/claude-code-action@v1
with:
# APIキーの場合
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# OAuthトークンの場合はこちら
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
これだけで、IssueやPRのコメントで @claude とメンションすると、Claude Codeが起動して返答してくれます。ここまでは簡単です。
問題はここからでした。
ハマったこと
CLAUDE.mdの@importが効いているのかわからない
ローカルのClaude Codeでは、CLAUDE.mdに @import を書いて別ファイルを読み込めます。
# CLAUDE.md
@docs/coding-guidelines.md
@rules/review-checklist.md
GitHub Actions上でもCLAUDE.mdは読み込まれます。リポジトリをcheckoutしているので、ファイル自体は存在しています。ただ、@import で参照したファイルが本当に読み込まれているのかが確認しにくい。
自分の場合、CLAUDE.mdから@importで文体の指示ファイルを読み込んでいたのですが、GitHub Actionsでの出力を見ると、指示した文体と全然違う応答が返ってきました。@importが効いていないのか、読み込まれているけどプロンプトの他の部分に負けているのか、切り分けができない。ローカルなら「CLAUDE.mdの内容を教えて」と聞けば確認できますが、GitHub Actionsでは対話ができないので、動作結果から推測するしかありません。
色々試した結果、最終的には@importのパスを間違えていただけでした😅ローカルでは補完が効くので気づけるミスが、GitHub Actionsだとエラーも出ずにただ無視される。地味ですが、こういうところが一番時間を溶かします。
確実に読み込ませたい内容は、ワークフロー内で明示的にファイルを読んで --system-prompt として渡すのが確実でした。--system-prompt はClaude Codeのシステムプロンプトに直接注入するオプションで、後述する prompt(タスクの指示を渡すパラメータ)とは別物です。
- name: Load context
id: context
run: |
{
echo 'CONTEXT<<CONTEXT_EOF'
cat docs/coding-guidelines.md
echo 'CONTEXT_EOF'
} >> "$GITHUB_OUTPUT"
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: '--system-prompt "${{ steps.context.outputs.CONTEXT }}"'
@import に頼らず、ワークフロー側で読み込みを制御する方が確実です。何が注入されているかがワークフローのYAMLを見れば明確なので、デバッグもしやすくなります。
PRを作れるようにする
Claude Codeにコードの変更をさせたいとき、PRとして提案してもらうのが安全です。ただ、PRを作るためには適切な権限設定が必要で、ここでもハマりました。
まず、ワークフローの permissions に pull-requests: write と contents: write が必要です。
permissions:
contents: write
pull-requests: write
issues: write
次に、allowed_tools でgitとghコマンドを許可します。claude-code-actionはデフォルトではツールの使用が制限されているので、明示的に許可する必要があります。
- uses: anthropics/claude-code-action@v1
with:
allowed_tools: "Bash(git:*),Bash(gh pr create:*),Bash(gh pr:*)"
allowed_tools の書き方でハマりやすいのが、スペースの有無や括弧の書き方です。Bash(git:*) のようにツール名とパターンを指定しますが、微妙に書き方を間違えると動きません。エラーメッセージも出ないことがあるので、PRが作られない場合はまずこの書き方を確認してみてください。
mainブランチへのコミットを制御する
GitHub Actions上でClaude Codeが動くとき、mainブランチがcheckoutされた状態で実行されます。ここで考えることは「mainへの直接コミットをどう扱うか」です。
claude-code-actionのTag mode(@claude メンション等)では、デフォルトで新しいブランチを作って作業する設計になっているので、mainへの直接pushは起きにくいです。一方、Agent mode(prompt 指定の自動化)では、allowed_tools でgit操作を許可すればmainへの直接コミットが可能になります。
コードの変更はPR経由にしたい場合
allowed_tools で git push を許可せず、gh pr create だけを許可するのが一番シンプルです。
allowed_tools: "Bash(gh pr create:*),Bash(gh pr:*)"
こうすればClaude Codeはmainに直接pushできず、変更は必ずPR経由になります。GitHubのブランチ保護ルールと併用するとさらに堅くなります。
もう少し柔軟に制御したい場合は、Claude Codeのhooks機能が使えます。hooksはツールの実行前後にスクリプトを挟める仕組みで、PreToolUse のタイミングでmainブランチへの操作だけをブロックできます。
#!/bin/bash
# .claude/hooks/pre-tool-use.sh
if [ "$GITHUB_ACTIONS" = "true" ]; then
BRANCH=$(git branch --show-current)
if [ "$BRANCH" = "main" ]; then
echo "mainブランチへの直接コミットはブロックされています。PRを作成してください。"
exit 1
fi
fi
$GITHUB_ACTIONS 環境変数で判定しているので、ローカルでは制限なし、GitHub Actionsでだけブロックする、という使い分けができます。
逆に、mainへの直接コミットをさせたい場合
毎回PRを作るまでもないタスクもあります。たとえばドキュメントの自動生成や、定期的なデータ更新など。わざわざPRを作ってマージするのは手間が増えるだけです。
この場合は allowed_tools で git commit と git push を許可します。
allowed_tools: "Bash(git add:*),Bash(git commit:*),Bash(git push:*)"
自分はエージェントの用途によって使い分けています。コードやワークフローを変更するエージェントはPR必須にして、ドキュメントやデータの更新だけを行うエージェントはmainへの直接コミットを許可する。「何を変更するか」でエージェントごとに権限を分けるのがポイントです。
まとめ
Claude Code × GitHub Actionsの組み合わせは、セットアップ自体は簡単ですが、実用的に使おうとするとローカルとの違いからくるハマりポイントがいくつかあります。
振り返ると、共通しているのは「ローカルでは対話で解決できることが、GitHub Actionsではできない」という点です。@importが効いているか確認できない、権限まわりのエラーが出ても対話で切り分けできない、危険な操作を目視で止められない。
対処の方向性はどれも似ていて、「暗黙的に動くことを期待せず、明示的に制御する」ということです。コンテキストはワークフローで明示的に渡す。ツールは必要なものだけ明示的に許可する。mainへのコミットは明示的にブロックする。
ローカルのClaude Codeで便利さを実感している方は、GitHub Actionsとの組み合わせも試してみる価値はあると思います。