はじめに
本記事では、Claude Code Maxを活用して、複数のAIエージェントを並列で稼働させ、継続的にissueを解決し続ける開発システムの実装方法を紹介します。参考レポジトリはこちらです。
このシステムは、Anthropicが公式に提供しているClaude Code用のdevcontainerをベースに拡張したものです。公式devcontainerには既にファイアウォールやセキュリティ設定が組み込まれており、これを活用することで、危険な--dangerously-skip-permissions
オプションを可能な限り安全に使用できるサンドボックス環境を実現しています。
参考までにシステム活用を始めてからの私のissue,PRの数を推移を記載しておきます。(本システム導入で簡易なissue,PRが乱立しがちになるので、生産性が等倍で上がっている、というわけではありません)
2025-06-18 Issues: 0 PRs: 5
2025-06-19 Issues: 0 PRs: 6
2025-06-21 Issues: 0 PRs: 1
2025-06-24 Issues: 0 PRs: 10
2025-06-25 Issues: 1 PRs: 6
2025-06-26 Issues: 0 PRs: 8
2025-06-27 Issues: 0 PRs: 6
2025-06-29 Issues: 0 PRs: 3
2025-06-30 Issues: 0 PRs: 6
2025-07-01 Issues: 0 PRs: 7
2025-07-02 Issues: 0 PRs: 24
システムのハイライト
- 完全自動化: issueを立てるだけで、エージェントが自動的に解決
- 並列処理: 複数のissueを同時に処理可能
- コンテキストフル: 実装だけではなく本番環境のアクセスログやデータベースを参照できるため、強力なコンテキストでissueを効率的に解決する
- ホストマシンの安全性: devcontainerによるサンドボックス環境で、claudeのコマンド実行からホストマシンを保護
- 情報セキュリティ: devcontainerのホワイトリスト形式のネットワーク制御により、取得したデータが不審なウェブに送信されるリスクを排除
- Maxプラン対応: Claude Code GitHub Actionsでもissueドリブン開発が可能だが、Maxプランを活用できずAPIキーによる従量課金対象になる。本メソッドはMaxプラン上で使用可能
--dangerously-skip-permissions
オプション利用への対策
本システムでは--dangerously-skip-permissions
オプションを使用していますが、これは以下の多層防御により運用されています:
1. ネットワークレベルの制御
- github.com(コード管理)
- rubygems.org(依存関係管理)
- AWS APIエンドポイント(ログ取得のみ)
2. 権限の最小化
リソース | 権限範囲 | 制限内容 |
---|---|---|
GitHubトークン | 特定レポジトリのみ | Fine-grained PATで必要最小限の権限 |
データベース | 読み取り専用 | UPDATE/DELETE/INSERT不可 |
AWS IAM | CloudWatch Logsのみ | 特定ロググループの読み取りのみ |
sudo権限 | 一部コマンドのみ |
3. データマスキング
本番データ → マスキング処理 → コンテナ内で利用
- 個人情報を匿名化
- 機密情報をダミーデータに置換
- ログのrequest_idは保持(デバッグ用)
4. コンテナ隔離
ホストマシン
│
├─ ホストのファイルシステム(アクセス不可)
├─ ホストのクレデンシャル(アクセス不可)
└─ devcontainer
├─ 独立したファイルシステム
├─ 制限された環境変数のみ
└─ ネットワークフィルタリング済み
これらの対策により、--dangerously-skip-permissions
オプションを使用しても、Claudeが:
- 意図しないサーバーにデータを送信することはできません
- ホストマシンの重要ファイルにアクセスすることはできません
- データベースを破壊することはできません
- 仮にプロンプトインジェクションを受けても、機密情報はマスキング済みです
アーキテクチャ要約
システムは以下の役割で構成されています:
- 人間
- devcontainerのコンテナを起動する
- Bossエージェント定期起動スクリプトを実行する
- GitHubのissueを作成し続ける
- Bossエージェント
- issueを監視し、issueに対応するWorkerエージェントを生成、アシスト、killする
- Workerエージェント群
- 個別のissueを解決する
Claude Codeとtmuxによる自動対話の仕組み
本システムの中核となるのは、Claude Codeをtmux経由で制御する仕組みです。
Claude Codeの基本動作
Claude Codeは、通常コマンドラインで以下のように対話的に使用します:
$ claude "質問やタスクを入力" --dangerously-skip-permissions
> Claudeのレスポンス...
> 次の入力を待機中...
tmuxを使った自動化
tmuxは端末多重化ツールで、仮想的な端末セッションを作成・管理できます。本システムでは、tmuxを使ってClaude Code Maxのセッションを制御します:
tmux new -d -s worker-1056 'claude --dangerously-skip-permissions'
tmux send-keys -t worker-1056 'タスクの内容を入力' Enter
tmux capture-pane -t worker-1056 -p
実際の動作フロー
以下の図は、BossエージェントがWorkerエージェントを制御する流れを示しています:
[Boss tmuxセッション]
↓
├─> issue #1234を発見
├─> tmux new -d -s worker-1234 でWorkerセッション作成
├─> tmux send-keys で "issue-solver.mdに従ってissue #1234を解決" を送信
├─> 定期的にtmux capture-paneで進捗確認
└─> 必要に応じて追加のプロンプトを送信
[Worker-1234 tmuxセッション]
├─> Claude Code Maxが起動
├─> issue-solver.mdの指示を受信
├─> issueの詳細を取得(gh issue view)
├─> コード変更を実施
└─> プルリクエストを作成
この仕組みにより、人間がキーボードで対話するのと同じように、Claude Code同士が対話できるようになります。
実装詳細
1. Boss(上司)エージェントの実装
Bossエージェントは、システム全体の司令塔として機能します。ボス自体がハングしてもいいように、devcontainer内にて以下のスクリプトでボスを5分おきに起動、シャットダウンを繰り返します。
#!/bin/bash
while true; do
echo "$(date): Boss agent restart"
git -C /workspace/repo pull
tmux kill-session -t boss 2>/dev/null || true
tmux new -d -s boss 'claude ".devcontainer/boss.md の指示に従って作業してください" --dangerously-skip-permissions'
tmux send-keys -t boss Enter
sleep 300
done
bossの主要な責務
- issue監視: 毎日のissueを確認し、未対応のものを検出
- Worker管理: 各issueに対してWorkerエージェントを生成し、ハングしていたら再稼働させる
- 状態管理: 完了したissueに対応するWorkerを終了
実装コード(boss.md抜粋)
本日のissue一覧を取得して、issue毎にtmuxのセッションを作成し、エージェントを起動します。
あなたは上司(boss)として複数のAIエージェントを部下として起動し指示を与えることができます。彼らを部下、あなた自身を上司(boss)として以下の方法で作業を進めてください
## あなたの仕事
- 次のコマンドを実行して、今日のissueを確認してください。
gh api graphql -f query='
query($endCursor: String) {
repository(owner: "repo", name: "owner") {
issues(first: 100, after: $endCursor, orderBy: {field: CREATED_AT, direction: DESC}) {
pageInfo {
hasNextPage
endCursor
}
nodes {
number
title
state
createdAt
}
}
}
}
' | jq -r --arg today "$(date -I)" '
.data.repository.issues.nodes[]
| select(.createdAt[0:10] == $today)
| "\(.number) [\(.state)] \(.title)"
'
- 今日のissueを確認します。もし今日にオープンなissueが1つも存在しない場合は、必ず終了してください。何もせずに終了することが正しい動作です。
- 今日のissueの中でまだオープンなものがあれば、対応の必要があります。次のパターンに従ってください。
- まずtmuxの全セッションを確認して、issue番号に対してセッション名に付番された番号と同じ部下が既に存在するか確認します。
- まだ存在しない場合は下記のコマンドでgit worktreeと部下を生成し、issueの解決を指示してください。
- もし、closeされたissueに対応する部下が存在する場合は、kill-sessionコマンドで部下を終了してください。
- 全issueへの対応が開始されたら、部下は途中で入力待ちでハングすることがあるので見回りが必要です。全sessionの作業内容を取得してみて、部下のtmuxの内容がプロンプト待ちになっていたら、追加のプロンプトを送信してください。何も無いときはsleep 60を実行して待機するループをしてください。
## git worktreeの生成方法
常にローカルにも、リモートにも存在しない、重複しない新規ブランチをデフォルトブランチ(develop)から作成してください。
## あなたの制約条件
- あなた自身もtmuxのプロセスです。セッション名はbossです。
- あなた自身はtmuxコマンドによる部下の生成と、その部下への指示出しのみが許可されています。それ以外のコマンドは使用できません。解決作業はすべて部下に行わせてください。
- あなた自身が入力待ちでハングすることを想定して、あなた自身は定期的に再起動されています。
## git worktreeの生成方法
常にローカルにも、リモートにも存在しない、重複しない新規ブランチをデフォルトブランチ(develop)から作成してください。
# worktreeを使っているのでls /workspaceで全ローカルのブランチを確認できる
ls /workspace
# リモートにブランチが存在するか確認する。suffixはアルファベット順にaからzまでです。
git ls-remote --heads origin fix/issue-1056-a
# 2a. リモートブランチが存在しない場合、新規作成する
git -C /workspace/prairie_card worktree add -b fix/issue-1056-a /workspace/fix-issue-1056-a develop
# 2b. 存在する場合、次のsuffixを調べて、存在しないならworktreeを作成する
git ls-remote --heads origin fix/issue-1056-b
git -C /workspace/prairie_card worktree add -b fix/issue-1056-b /workspace/fix-issue-1056-b develop
## エージェントとの通信方法
tmux ls # 部下の一覧を取得 ※上司自身が再起動しており、すでに以前に作った部下が存在している可能性がある
tmux new -d -s worker-1056 'cd /workspace/fix-issue-1056-a && claude --dangerously-skip-permissions' && sleep 3 # 部下の新規起動(issue#1056に対応する例)。プロンプトが入力可能になるまで時間がかかるので、3秒待つ
# 全sessionの作業内容を取得
tmux list-sessions -F '#{session_name}' | while read session; do
echo "=== Session $session ==="
tmux capture-pane -t $session -p | tail -n 100
echo ""
done
tmux send-keys -t worker-1056 'docs/issue-solver.mdの指示に従い、issue#1056を解決してください' C-m && sleep 1 && tmux send-keys -t worker-1056 C-m # 部下に指示を入力・送信。submitできてない時が多発するので再submit(C-m)までを必ずセットに
tmux send-keys -t worker-1056 Down Down Enter # 部下のclaudeが選択肢を提示しているときに適切なメニューを選択する
tmux kill-session -t worker-1056 # issueが既にクローズされていたら、部下のセッションを終了する
2. Workerエージェントの実装(issue-solver.md抜粋)
各Workerエージェントは、個別のissueを解決する責任を持ちます。
issue-solver.mdはレポジトリにより大きく異なると思うので抜粋にとどめます。
# Issue Solver ガイド
## 概要
このドキュメントは、GitHubのissueを効率的に解決するための手順を記載しています。指示を受けた際に、このガイドに従って作業を進めてください。
## 作業開始前の準備
### 1. 対象issueの詳細把握
#### 1.1 issueの基本情報取得
# issueの詳細を取得(全コメントを含む)
gh issue view {issue番号} --comments
### 2. issueの原因分析
#### 2.1 関連ファイルの特定
#### 2.2 既存の実装を理解
#### 2.3 アクセスログの確認
issueにrequest_idが記載されている場合は、それを元にアクセスログを確認してください。重要なヒントになります。取得できたアクセスログは可能な限りの詳細をissueにコメントして報告してください。アクセスログが取得できたら、次にデータベースの確認に進んでください。
aws logs start-query \
--log-group-name /log-group-name \
--start-time $(date -v-7d +%s) \
--end-time $(date +%s) \
--query-string 'fields @timestamp, @message | filter @message like // | sort @timestamp desc | limit 20'
sleep 10 # クエリ実行に時間がかかるので、まずは10秒待つ
# クエリIDが返るので、結果取得。もしまだ結果が返ってこない場合は、10秒待つ。
aws logs get-query-results --query-id
#### 2.4 本番データベースから原因を特定
本番には以下のコマンドでSQLを実行できます。SELECT文のみ実行できます。アクセスログから得られるPOST内容やURLから判別できるリソースのレコードを調査することができます。
PaperTrail用のversionsテーブルを参照すれば、リクエストがあった時点のレコードの内容を推測することもできます。
接続ができないときは、本issueの調査では許可がされていないという意味なので、諦めてください。
本番のデータは重要なヒントになります。本件に参考にすべき必要なデータの場合は、issueに可能な限りの詳細をコメントして報告してください。
# PAGERをOFFにしてSQLクエリを実行(例としてuserのuuidが判明して、ユーザー情報を取得する例)
psql $DATABASE_URL -P pager=off -c "SELECT * FROM users WHERE id = '5a781342-7657-4309-91f8-56de8269c98b';"
### 3. 対応方針の判断
#### 3.1 プルリクエストの必要性を判断
調査結果に基づいて、以下の観点から対応方針を決定してください:
**プルリクエストが必要な場合:**
- コードの変更が必要
- 設定ファイルの修正が必要
- テストの追加・修正が必要
- ドキュメントの更新が必要(issueで明示的に要求されている場合)
**プルリクエストが不要な場合(調査・報告のみ):**
- 調査結果の報告のみで問題が解決する
- 既に修正済み・解決済みの問題
- 仕様通りの動作であることの確認
- 運用での対応が適切な場合
⚠️ **重要**: どちらの場合でも、issueへの詳細な報告とissueのクローズは必須です。
プルリクエストが不要な場合は、セクション9「issueの更新」に進んでください。
### 4. テスト駆動開発(TDD)
**※ プルリクエストが必要な場合のみ、以下の手順を実施**
#### 4.1 失敗するテストを先に書く
# テストを実行して失敗することを確認
bundle exec rspec spec/関連ファイル_spec.rb:行番号
# 失敗することを確認(期待される動作)
echo "✅ テストが失敗することを確認しました"
#### 4.2 失敗するテストをコミット
# 失敗するテストのみをステージング
git add spec/関連ファイル_spec.rb
# RuboCopでコードスタイルをチェック
bundle exec rubocop app/関連ファイル.rb
# 必要に応じて自動修正
bundle exec rubocop -a app/関連ファイル.rb
# 失敗するテストをコミット(--no-verifyでローカルCIのfailを回避)
git commit --no-verify -m "test: issue #{issue番号}の失敗するテストを追加
- 期待される動作のテストケースを追加
- 現在の実装では失敗することを確認"
# プッシュ
### 5. 実装作業
#### 5.1 実装
- 必要なファイルを編集
- コードの品質を保つ(RuboCop準拠)
- 日本語コメントを適切に記述
#### 5.2 動作確認
### 6. 品質チェック
**※ プルリクエストが必要な場合のみ、以下の手順を実施**
#### 6.1 コード品質
#### 6.2 テストカバレッジ
### 7. コミットとプッシュ
**※ プルリクエストが必要な場合のみ、以下の手順を実施**
#### 7.1 変更内容の確認
# 変更されたファイルを確認
git status
# 差分を確認
git diff
#### 7.2 実装をコミット
`docs/commit.md`を参照してコミットを実行してください。
### 8. プルリクエスト作成
**※ プルリクエストが必要な場合のみ、以下の手順を実施**
`docs/create-pull-request.md`を参照してプルリクエストを作成してください。
### 9. CI確認(必須)
**⚠️ 重要: プルリクエストを作成した場合、CI確認は必須です。CIが全て通過していることを確認せずに作業を完了してはいけません。**
**※ プルリクエストが必要な場合のみ、以下の手順を実施**
#### 9.1 CI状況の確認(必須)
プルリクエスト作成後、**必ず**以下の手順でCI確認を行ってください:
# PR番号を確認
PR_NUMBER=$(gh pr list --head $(git branch --show-current) --json number --jq '.[0].number')
echo "PR番号: $PR_NUMBER"
# CI確認を実行(必須)
source scripts/ci-check-functions.sh && check_pr_ci $PR_NUMBER
#### 9.2 CI失敗時の対応(必須)
**⚠️ CIが失敗している場合は、必ず修正が必要です:**
1. 失敗しているワークフローの詳細を確認
2. エラーメッセージを分析し、原因を特定
3. 必要な修正を実施
4. 修正をコミット・プッシュ
5. **再度CI確認を実行し、全てのチェックが通過するまで繰り返す**
# 失敗したワークフローの詳細確認
source scripts/ci-check-functions.sh && check_failed_workflow
# 修正後、再度CI確認(必須)
source scripts/ci-check-functions.sh && check_pr_ci $PR_NUMBER
**🚫 注意: CIが全て通過していない状態でissueをクローズしてはいけません。**
### 10. issueの更新
#### 10.1 作業完了時の詳細報告とクローズ
# 作業完了時の詳細な報告コメントを作成
cat > temp_completion_report.md
3. devcontainer環境の構築
セキュリティの要となるdevcontainer環境の実装です。Anthropic公式のdevcontainerをDockerfileのマルチステージのベースにして、次の開発に必要なツールを追加しています。
- Rails, PostgreSQLなど
- Cloudwatch LogsはAPIのIPが動的でホワイトリスト対応ができなかったので、ホワイトリスト制約外のaws実行専用ユーザーを作り、このsuするとこのコマンドだけ実行できる形にして迂回しています(init-firewall.shに追記)
- bundle installのためrubygems.orgの追加(init-firewall.shに追記)
Dockerfile.custom(拡張用Dockerfile)
FROM claude-dev
USER root
RUN apt update && apt install -y tmux \
locales \
locales-all \
fonts-noto-cjk \
postgresql \
postgresql-contrib \
libpq-dev
RUN ARCH=$(uname -m) && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
rm -rf aws awscliv2.zip
RUN git clone https://github.com/rbenv/rbenv.git /opt/rbenv && \
git clone https://github.com/rbenv/ruby-build.git /opt/rbenv/plugins/ruby-build
RUN service postgresql start && \
su - postgres -c "createuser -s node" && \
su - postgres -c "createdb -O node project_development" && \
su - postgres -c "createdb -O node project_test" && \
service postgresql stop
RUN useradd -r -s /usr/sbin/nologin awscli
RUN echo "node ALL=(awscli) NOPASSWD: SETENV: /usr/local/bin/aws-original" > /etc/sudoers.d/awscli-call
USER node
使い方
project-cli(仮名)というCLIを作ってレポジトリを運用しており、以下の手順で実行しています。cliを導入したくない場合は、次のコードの中身を直接実行してください。
project-cli claude reset && project-cli claude boss
project-cli claude bash
tmux a
tmuxでbossのセッションに入ると、Claude Code Maxの連携が促されるので、ブラウザを開いてトークンをペースとしたり、 –dangerously-skip-permissionsの免責事項の許諾を行う必要があります。これは次からのbossセッションの起動では不要であり、Dockerのコンテナ単位で必要になります。
これでbossが稼働したら、本日付のオープンなissueの検索が始まります。
CLIツールセットと、日々の使い方
project-cliというレポジトリ専用のコマンドセットを作っており、そのサブコマンドセットとしてclaudeを用意しています。
project-cli claude stop
project-cli claude build
project-cli claude run
project-cli claude reset
project-cli claude boss
project-cli claude bash
project-cliの実装詳細
( claude )
ARG=${2}
case $ARG in
( stop )
echo "全tmuxセッション(boss, worker)を終了します"
docker stop project-claude-dev 2>/dev/null || true
docker rm project-claude-dev 2>/dev/null || true
;;
( build )
echo ".devcontainer配下のコンテナをビルドします"
docker build -t claude-dev .devcontainer
docker build -t project-claude-dev -f .devcontainer/Dockerfile.custom .
;;
( run )
echo ".devcontainer配下のコンテナをバックグラウンド起動します"
docker run -d --name project-claude-dev \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
-e GITHUB_TOKEN=${GITHUB_TOKEN_CLAUDE_PAT} \
-e READONLY_PRODUCTION_DATABASE_URL=${READONLY_PRODUCTION_DATABASE_URL_FOR_CLAUDE} \
-e AWS_ACCESS_KEY_ID=${APPRUNNER_LOGS_USER_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${APPRUNNER_LOGS_USER_SECRET_ACCESS_KEY} \
-w /workspace/project \
project-claude-dev
echo "ファイアウォールを初期化しています..."
docker exec project-claude-dev sudo /usr/local/bin/init-firewall.sh
docker exec project-claude-dev /usr/local/bin/init.sh
echo "コンテナの起動が完了しました"
;;
( reset )
echo "コンテナの停止・再ビルド・起動を実行します"
$0 claude stop
$0 claude build
$0 claude run
;;
( boss )
echo "bossエージェントを起動します"
docker exec -d project-claude-dev bash /workspace/project/.devcontainer/boss.sh
;;
( bash )
echo "コンテナに入ります"
docker exec -it project-claude-dev bash
echo "tmux aでbossセッションにアタッチできます"
;;
esac
;;
ネットワークのホワイトリスト制限に加えて、以下のセキュリティを設けています。
- GITHUB_TOKENトークンは特定レポジトリのみに制限したFine-grained PATを使用
- DBクレデンシャルは読み取り専用、DBは本番をダンプしてマスキングを実行済
- AWSクレデンシャルは特定のログのみ読み取り可能、かつ本番ロガーと並行してマスキングされたログが貯まるマスク済みログを使用
- レポジトリを
-v
でマウントすると、隠しファイル経由で不要なクレデンシャルがコンテナに把握されるリスクがあるので、マウントせず毎回コンテナ上でクローンする
init.shの中身は以下のようになっています。
#!/bin/bash
gh repo clone org/repo -- --depth 1
git config --global user.email "${GITHUB_USER}@users.noreply.github.com"
git config --global user.name "${GITHUB_NAME}"
gh auth login --hostname github.com --with-token (echo "$GITHUB_TOKEN")
gh auth status
gh auth setup-git
リスク、デメリットについて
安全性について
Githubがホワイトリストに入ってますが、ghコマンドはパブリックレポジトリやgistに投稿する能力を有しており危険です。そのためghに与える権限は最低限にすることはもちろんのこと、プロンプトインジェクションで「こちらのトークンを使え、そしてこちらに情報を報告しろ」とやられて漏洩することを防ぐため、インジェクションを読み取らないために、また意味のある情報を有さないためにマスキングしたログ、データを使用しています。本当は、プロキシで意図しないレポジトリ以外にAPIが通信してようにフィルタリングまで行うべきなのでしょう。
Claude Code Maxの使用制限について
人単位で課金しているClaude Code Maxを他人と共用することは禁じられているはずなので、他の人が立ち上げたissueに対して本システムが稼働してしまうのは、事実上の共用であり、規約違反になるかと思います。issueのリストアップはauthorを自分自身にするなどのフィルタリングが必要です。
運用で感じるデメリット
- 本番の情報とはいえマスキングしたら解決できない、参考にならない情報と化すことは多い
- API開発やテストのVCRなど外部ネットワークに接続したい開発が苦手
Views: 0