この記事の前提
別記事『Claude CodeとCodexで個人アプリ開発を半自動化した話』で、Claude Code(機能開発担当)と Codex(レビュー担当)の 2 つの AI エージェントに 7 日間でアプリ開発を任せた話を書きました。
その記事ではワークフロー全体の設計と運用結果を整理しましたが、実際にこの仕組みを組んで動かす過程では、設計時点では見えていなかった想定外の壁が次々に出てきました。
本記事は、開発ワークフローの構築・運用でハマった 6 つのワナについて、 症状 → 原因 → 対応 の順でまとめたものです。同じような仕組みを組もうとしている方が、同じワナを踏まずに済むよう、できるだけ一般化した形で残しておきます。
仕組みの全体像はメイン記事『Claude CodeとCodexで個人アプリ開発を半自動化した話』に、個々の設計判断の根拠は『Claude Code と Codex の半自動開発ワークフローの設計詳細』に整理してありますので、それらも合わせて参照してください。
ワナ 1: Free プラン制約で auto-merge も branch protection も使えない
最初に当たった壁は、設計の前提そのものでした。
当初のワークフロー設計では、main ブランチに対して「PR 必須」「CI green 必須」「Codex によるレビュー承認必須」「conversation resolved 必須」というブランチ保護を敷くつもりでした。加えて、Codex がレビュー OK と判定した PR は GitHub の auto-merge 機能で自動マージされる、というのが想定の流れです。
しかし検証してみると、 GitHub Free プランのプライベートリポジトリでは auto-merge も branch protection もそもそも利用できない ことが判明しました。Pro 以上の有料プランへの切り替えが必要です。
対応としては、二層構造で代替しました。
- プロンプト側ルール: 「Claude Code は自分の PR をマージしない」「Codex は CI green を確認してからマージする」をプロンプトに明記し、エージェントの行動として厳守させる
-
.claude/settings.local.jsonの deny リスト: Claude Code に対してgh pr mergeを deny 登録し、誤発火を二重に防止する
GitHub が機能で保証してくれるはずだった安全弁が使えないため、プロンプトと設定ファイルの 2 段で「マージしてはいけない側がマージしない」状態を担保する形になりました。Pro プランに切り替えれば一発で解決する話ではありますが、個人開発の動作確認レベルなら、この多層防止で十分実用に耐えました。
ワナ 2: 別 GitHub App 発行を諦め、同一アカウント運用に切り替えた
設計上の前提崩しは、もう 1 つありました。
当初は Codex と Claude Code をそれぞれ別の GitHub App として発行 し、Codex 側の App が PR の review API に承認コメントを残すことで、GitHub の self-review 禁止仕様によって「実装者と承認者が別人格である」状態を担保するつもりでした。
しかし検証の過程で、個人開発レベルでは GitHub App の発行・運用コストが過剰だと判断し、 Codex / Claude Code 双方を私のローカル Mac 上で同一アカウントとして動かす 方針に切り替えました。
この切替で副作用が 1 つ発生します。同一アカウントで動く以上、GitHub 側の self-review 禁止仕様が「Claude Code が自分の PR を承認する」ケースを防いでくれません。
対応として、PR review API による承認 gating そのものを諦め、以下の運用に変更しました。
- レビューは GitHub の review API ではなく PR コメント として残す
- マージは Codex の PR レビューバッチが OK 判定時に
gh pr merge --merge --delete-branchで直接実行する - 「Claude Code は自分の PR をマージしない」をプロンプト側ルールとして厳守させる
GitHub が本来防いでくれるはずの安全弁を、プロンプトの行動制約に置き換える形です。エージェントが従順であることへの依存度が高まるため、 プロンプトを書く側の責任が一段重くなる という構造変化が起きました。
ワナ 3: GitHub API ではなく gh CLI に統一する必要があった
各バッチの実装当初、Codex の自動化は GitHub API を直接叩く構成を一部試していました。しかし運用していくうちに、認証エラー / 権限スコープの不整合 / 環境差によるレスポンス挙動の違いが散発的に出始め、バッチが落ちる原因の上位に入ってきました。
対応として、Codex / Claude Code 双方のすべての GitHub 操作を gh CLI ベースに統一しました。具体的には以下の構造です。
- バッチ冒頭で
gh auth status -h github.comの成功を必ず確認し、失敗した場合は処理を中断 -
git remoteの URL が想定通りであることを確認 -
GIT_TERMINAL_PROMPT=0 git pull --ff-only origin mainで、認証プロンプトが必要な状態になったら確実に中断する
gh CLI に統一すると、認証は OS のキーチェーンに集約され、API の差異も CLI 側が吸収してくれます。直接 API を叩く設計より、CLI を 1 段挟む方が、AI エージェントを介した運用とは相性が良いと感じました。
ワナ 4: コメント先頭のインデントでマーカー行が機能しなくなる
このワークフローでは、Codex の詳細化結果やレビュー結果を Issue / PR の コメント として追記し、その冒頭に以下のようなマーカー行を置きます。
## [Codex 詳細化 (2026-05-22)]
Claude Code はこのマーカーで最新の Codex コメントを機械的に検索する設計です。
ところがある時、Issue の詳細化コメントを Claude Code がうまく拾えない事象が発生しました。原因を追うと、Codex が一時ファイルを生成する際にインデント付きの heredoc(シェルスクリプトなどで複数行テキストを扱う記法)を使っていて、 ファイル全体の各行頭にスペースが入った状態で gh issue comment --body-file に渡されていた ことが判明します。
GitHub の Markdown レンダラは行頭スペースが 4 つ以上の行をコードブロックとして扱うため、本来 H2 として解釈されるはずの ## [Codex 詳細化 ...] 行が、装飾なしのプレーンテキスト(コードブロック内の 1 行)として扱われていました。Claude Code 側の検索ロジックは H2 構造として残ったマーカー行を期待していたため、マッチしないコメントだけが量産される結果になっていた、というわけです。
対応として、Codex のプロンプトに以下の固定手順を追加しました。
- 一時ファイル書き出しはインデント付き heredoc /
textwrap.dedent前提の文字列を使わない -
cat > /tmp/xxx.md <<'EOF'以降は、 行頭からマーカー行を始める - 投稿前に
head -n 1 /tmp/xxx.mdの出力がマーカー行と完全一致することを確認する
たかが行頭スペースの話に見えますが、AI エージェント同士の文字列ベースの連携では、表記の揺れがそのまま機能の欠落に直結します。 エージェント間の通信プロトコルは、人間用ドキュメントの慣習よりも厳密に守らせる必要がある という気づきが残りました。
ワナ 5: ブランチ名が命名規約に従わなくなる
Claude Code を Scheduled Task として動かす際、worktree モード(タスク起動時に隔離されたブランチとディレクトリを自動生成するモード)を使っていました。このモードでは、タスク起動時に claude/* や cc/* で始まる自動命名ブランチが切られます。
ある日のバッチ完了後、リモートに claude/abc123 のような 命名規約違反のブランチが残っている ことに気づきました。本来は feat/YYYYMMDD-issue-12-15-18 の命名規約に沿うべきところです。
原因は単純でした。Issue 実装バッチのプロンプトは「現在のブランチが main なら新規ブランチを切る」という前提で書かれていて、worktree モードで起動した自動命名ブランチに対しては、git checkout -b の代わりにリネーム処理が一切入っていなかったのです。結果、Claude Code は自動命名ブランチのまま実装を進め、最後にそのままの名前で push してしまった、というわけです。
対応として、プロンプトの作業前チェックを以下のように分岐させました。
- 現在のブランチが
mainの場合:git checkout -b <目標ブランチ名>で新規ブランチを切る - 現在のブランチが
claude/*またはcc/*で始まる自動命名ブランチの場合:git branch -m <目標ブランチ名>で 現在のブランチをリネームする
加えて、push 前に正規表現 ^feat/[0-9]{8}-issue-[0-9-]+$ でブランチ名を検証し、命名規約違反の場合は push せず中断するチェックも入れました。
エージェントを動かす実行環境(worktree モード)の振る舞いを、プロンプト側が知らない状態で動かすと、出力(リモートに残るブランチ名)に副作用が出る。 プロンプトは「自分が動く実行環境」を前提に書く必要がある という、当たり前のことを実例で再確認した形です。
ワナ 6: 1 日 1 回で安定したから、6 倍に上げた瞬間に出てきた問題
最後のワナは、運用初期の慎重さがそのまま遅延につながった話です。
最初の試験運用では、各バッチを 1 日 1 回 からスタートさせました。暴走 / 滞留が起きないことを 2 日間ほど確認してから、3 日目に 1 日 6 回(4 時間サイクル)に切り替える、というのが当初の計画です。
実際にやってみると、1 日 1 回の試験運用は確かに安定して見えました。しかし、頻度を 6 倍にした瞬間に、ここまでに挙げた ワナ 4(マーカー行のインデント問題)とワナ 5(worktree モードのブランチ問題)が連続して顕在化 しました。
理由を考えてみると単純で、1 日 1 回の頻度ではこれらの問題は偶然踏まなかっただけ、もしくは踏んでいても 1 件だけだったので、私が問題として認識できていなかっただけだったのです。6 倍にすると、同じ確率の問題が 6 倍の頻度で表に出るため、再現性のある問題として捉えられるようになる。低頻度では潜伏する問題が、頻度を上げると一気に表面化する、という現象でした。
ここから得た教訓は、頻度設計を 2 段で考えるべきだったということです。低頻度運用は、ワークフローが暴走しないことの確認には確かに有効でした。一方で、 発生頻度が少ない潜伏バグにはなかなか気づけません。 試験運用に低頻度ステージしか置かないと、後者を漏らしたまま本格運用に入ってしまうリスクがあります。
私の場合は、結果として頻度を上げた日にワナ 4 とワナ 5 が出て、その日のうちに修正できました。ただ、もし試験運用期間を 1 週間に延ばしていたら、これらのワナは本格運用入りの後に出てきていた可能性が高い。短期間でも高頻度運用の負荷試験を入れる というステージを試験運用に組み込むのが、今回の振り返りとして残った観点です。
ワナは性格別に 3 つのレイヤーに分かれる
6 つのワナを振り返ってみると、性格の違う 3 つのレイヤーに分かれることがわかります。
- 設計前提の崩壊: ワナ 1(Free プラン制約)/ ワナ 2(GitHub App 発行断念)
- エージェント間の通信プロトコルと実行環境の前提: ワナ 3(gh CLI 統一)/ ワナ 4(マーカー行のインデント)/ ワナ 5(worktree モードのブランチ)
- 試験運用の頻度設計: ワナ 6(1 日 1 回 → 6 倍のワナ)
それぞれのワナは単独で見ればごく小さなものですが、6 サイクル / 日で動くバッチでは見えやすくなります。逆に言えば、 低頻度のバッチでは、こうした問題は気づかれないまま潜伏する ということでもあります。
半自動開発の頻度を上げると、設計の歪みが正直に表に出てくる。これは運用してみるまで予想していなかった副次効果でした。
仕組み全体の設計と、これらのワナがどの文脈で出てきたかについては、メイン記事『Claude CodeとCodexで個人アプリ開発を半自動化した話』に整理しています。個々の設計判断の根拠を深堀りした『Claude Code と Codex の半自動開発ワークフローの設計詳細』と併せて読むと、同じ仕組みを組むときの解像度が一段上がるはずです。
この記事と合わせてご参照ください。