はじめに
Codex CLIにIssueの実装からPR作成、CI・Codexレビューへの対応まで任せていたところ、GitHubの状態確認を何度も繰り返していることに気づきました。
例えば、次のようなメッセージです。
- CIの成功状態に変化はありません。CIの結果が分かるまで待機します。
- Codexレビューはまだ完了していません。
1回のPRで平均5〜6回、多いときには10回以上も表示されていました。
これだけ何度も確認しているなら、そのたびにLLMが呼び出され、大量のトークンを消費しているはずです。
そこで待機方法を見直したところ、CI監視中のLLM呼び出し回数と入力トークン数を約9割削減できました。
想定読者
- Codexでの実装タスクにおけるトークン消費が気になっている方
- CI・レビューが完了するまでCodexに任せている方
改善①:GitHub CLIにCIの監視を任せる
最初にセッションログを調べたところ、GitHubの状態を確認するたびにツールの結果がLLMへ返され、次の確認処理を判断していることが分かりました。
そこで考えたのが、CIの監視をGitHub CLIに任せ、完了したときだけCodexに結果を返す方法です。
従来:
Codex → GitHub確認 → Codex → GitHub確認 → ...
改善後:
Codex → 監視コマンドを実行 → CI完了 → Codex
調べてみると、GitHub CLIにはgh pr checks --watchというコマンドがありました。
gh pr checks <PR番号> --watch --interval 30
これは30秒ごとにGitHubへ問い合わせ、PRのCIチェックが完了するまで監視を続けるコマンドです。
つまり、Codexがこのコマンドを1回実行して終了まで待てば、CIの状態を確認するたびにLLMが判断する必要はなくなります。
ただし、ここで1つ問題があります。
Codexがコマンドを実行する際、一定時間が経過しても処理が終了しなければ、ツールが次のような結果を返すことがあります。
Process running with session ID 12345
これはCIが完了したという意味ではなく、コマンドがまだ実行中であることを示しています。
このsession_idは、実行中のコマンドを識別するためのIDです。CodexはこのIDを使って、同じコマンドの終了を引き続き待つことができます。
そのために使うのがwrite_stdinです。
write_stdinは、実行中のコマンドとやり取りするためのツールで、session_idを指定すると、コマンドを再実行せずに、その終了や出力を待つことができます。
例えば、次のように呼び出します。
{
"session_id": 12345,
"chars": "",
"yield_time_ms": 300000
}
-
session_id:待機するコマンドを指定する。 -
chars: "":何も入力せずに待つ。 -
yield_time_ms: 300000:最大300秒(5分)待機する。
5分は最大待機時間です。コマンドがそれより早く終了すれば、その時点で結果を受け取れます。
これなら、gh pr checks --watchにCIを監視させ、その終了をwrite_stdinで長時間待つことで、無駄なLLM呼び出しを減らせると考えました。
そこで、独自に作成していたGitHub操作用Skillに、この待機方法を記述しました。
失敗:ツールの待機構造への理解不足
実際に試してみると、LLMの呼び出し回数は全く減りませんでした。
セッションログを確認すると、そもそもwrite_stdinに300秒が指定されておらず、最大60秒程度の待機になっていました。
さらに調べると、write_stdin以外にも待機時間に関わるツールがあることが分かりました。
| ツール | 役割 |
|---|---|
functions.exec |
JavaScriptを実行し、その中からwrite_stdinなどのツールを呼び出す |
write_stdin |
実行中のコマンドの終了や出力を待つ |
functions.wait |
途中で返却されたfunctions.execの続きを待つ |
ここで重要なのは、write_stdinと、それを呼び出すfunctions.execに、それぞれ待機時間があることです。
例えば、functions.execからwrite_stdinを呼び出し、60秒間待とうとします。
しかし、外側のfunctions.execが30秒で途中返却すると、内部のwrite_stdinが待機を続けていても、LLMに制御が戻ります。
Codexはfunctions.waitを呼び、残りの処理を待とうとします。ところが、functions.waitの待機時間も短いと、再びLLMに制御が戻ってしまいます。
実際のセッションログでも、次の動作を確認できました。
| ツール | 当時の動作 |
|---|---|
write_stdin |
60秒の待機を指定 |
functions.exec |
約30秒で途中返却 |
functions.wait |
1秒・10秒程度の待機を繰り返す |
Codex v0.163.0のソースコードを確認すると、デフォルトの待機時間はfunctions.execが30秒、functions.waitが10秒でした。
つまり、最初の改善には次の2つの問題がありました。
- Skillに5分待機と書いても、実際の
write_stdinにはその値が指定されていなかった。 - 仮に
write_stdinを5分にしても、外側のfunctions.execやfunctions.waitが短時間で返却してしまう。
無駄なLLM呼び出しを減らすには、3つのツールの待機時間を見直す必要があったわけです。
改善②:config.tomlで待機時間を5分に変更する
Codexのソースコードと設定ファイルを調べ、3つのツールの待機時間を最大5分に変更することにしました。
functions.execのデフォルト待機時間は、~/.codex/config.tomlで変更できます。
一方、write_stdinとfunctions.waitには、同様の設定項目が見つかりませんでした。
そこで利用したのが、LLMに共通の指示を追加できるdeveloper_instructionsです。
最終的に、config.tomlへ次の設定を追加しました。
developer_instructions = """
長時間実行中の処理を待機する場合:
- functions.wait は yield_time_ms=300000 を優先する。
- write_stdin の空入力も yield_time_ms=300000 を優先する。
- 途中経過の確認だけを目的とした短時間の待機を繰り返さない。
"""
[features.code_mode]
enabled = true
default_exec_yield_time_ms = 300000
この設定で、functions.execのデフォルト待機時間を300秒に変更できます。write_stdinとfunctions.waitには、最大300秒の待機を優先するよう指示しています。
※ developer_instructionsはLLMへの指示なので、必ず指定どおりに動作する保証はありません。
検証結果:LLM呼び出しが16回から2回に減少
設定を変更した後、GitHub Actionsを手動実行し、Codexに完了まで監視させました。
検証にはgh run watchを使い、設定変更前後でそれぞれ約4分間のCIの監視を比較しました。
| 指標 | 改善前 | 改善後 | 削減率 |
|---|---|---|---|
| LLM呼び出し | 16回 | 2回 | 87.5% |
| 入力トークン | 587,257 | 65,982 | 88.8% |
改善前は、functions.execとfunctions.waitが短時間で制御を返すたびに、LLMが呼び出されていました。
改善後はwrite_stdinがCIが動作していた約4分間連続で待機し、その間にLLMが呼び出されることはありませんでした。
この実験ではfunctions.waitが呼ばれなかったため、別途functions.execの待機時間を短くして検証し、functions.waitでも約5分の連続待機を確認できました。
考察:5分待機による影響
今回、3つのツールの待機時間を最大5分に変更しましたが、デフォルトをそこまで長くして問題ないのでしょうか。
CI監視では、コマンドが正常に終了すれば5分を待たずに結果が返ってきます。そのため、待機時間を延ばすことによる影響は小さいと考えました。
一方、処理が停止した場合や、途中で入力が必要になった場合には、問題に気づくのが遅れる可能性があります。
それでも、私の場合は数分程度でCIが完了するため、待機時間を延ばすメリットの方が大きいと判断しました。
ただし、1時間かかるようなCIでは、5分待機でも何度もLLMが呼ばれる可能性があります。そうした場合は、Webhookなどで完了を通知する仕組みの方が適しているかもしれません。
おわりに
今回特に驚いたのは、単にCIの完了を待っているだけで、約60万もの入力トークンが発生していたことです。
その大部分はキャッシュ入力でしたが、待機時間を見直すことで大幅に削減できました。
ひとまずCIの監視はかなり改善できたので、よかったです。
一方、Codexレビューの完了をイベント駆動で検知し、ローカルのCodexを自動再開する仕組みは、まだ実装できていません。こちらもいつか改善したいと思っています。