コーディングエージェントの能力差に見えた失敗の原因はAPIキーの渡し方

English

この記事の目次

本記事の概要

統合エージェント開発環境をOSSで組み直す研究の公開予定リポジトリで、AIエージェントが3つのコーディングエージェントを比べた。モデルと課題は同じにそろえ、課題は8つである。素材は、このリポジトリの記録である。この比較では、親エージェントが並列実行のMCPサーバを呼び出す。並列実行のMCPサーバは研究の中で自作したプログラムで、呼び出されると子エージェントを並列に起動する。親がOpenHands CLIの3回の実行でだけ、起動した子エージェントの合計32件が1件も完了しなかった。実行記録のファイルの状態の欄も、測定の結果の欄も正常のままで、この結果は約1日のあいだ、比較の結果として扱われた。

原因は、コーディングエージェントの能力の差ではなく、測定用のスクリプトの作りにあった。測定用のスクリプトは、子エージェントが使うAPIキーの受け渡しを、親プロセスの環境変数の引き継ぎと、.envファイルの読み込みに任せていた。親がOpenHands CLIで、隔離した作業ディレクトリで実行した場合だけ、どちらの経路でもAPIキーが届かなかった。

AIエージェントは、APIキーをMCPサーバの設定のenv欄に書いて渡す形に、測定用のスクリプトを修正した。修正した後、親がOpenHands CLIの条件を、指示ファイル一式を置く場合と置かない場合の2つで、1回ずつ測り直した。指示ファイル一式は、作業ディレクトリに置くAGENTS.mdなどのファイルの組である。どちらの条件でも、APIキーが原因で子エージェントがすべて失敗することはなくなった。指示ファイル一式を置いた条件では、子エージェント8件がすべて完了した。置かない条件では、起動した16件のうち15件が、APIキーとは別の理由で完了しなかった。

本文は、最初の節で原因と修正の全体像を示す。その後の節は、次の順に並べた。

本文では、比べた3つのコーディングエージェントの製品名を出す。内容は各提供元の見解を代表するものではない。

記事から得られること

コーディングエージェントからMCPサーバを経由して別のエージェントを起動する方は、次の3つができるようになる。

この記事は、研究の公開予定リポジトリの実行記録と、執筆時に確認した公開のソースコードに基づく考察である。


APIキーの受け渡しが依存していた2つの暗黙の経路

この節では、比較の測定で子エージェントがすべて失敗した原因と、修正した後の形を先に説明する。

比較の測定は、3つのコーディングエージェントを、同じモデルと同じ課題で、条件を変えながら順に測った一連の実行である。コーディングエージェントは、端末から起動して、モデルを呼び出しながらファイルを編集し、コマンドを実行するCLIのプログラムである。比較の測定では、親エージェントが並列実行のMCPサーバを呼び出す。親エージェントは、課題を受け取って最初に起動されるコーディングエージェントのプロセスである。

並列実行のMCPサーバは研究の中で自作したMCPサーバで、呼び出されると子エージェントを並列に起動する。MCPサーバは、コーディングエージェントが呼び出すツールを、MCPという共通の手順で提供するプログラムである。この記事で扱うMCPサーバは、エージェントが子プロセスとして起動し、標準入出力で通信するstdio形式である。子エージェントは、並列実行のMCPサーバが起動するコーディングエージェントのプロセスである。

測定用のスクリプトは、親エージェントに課題を渡して起動し、結果をファイルの実体から判定して記録するPythonのスクリプトである。修正の前は、子エージェントのAPIキーが、次の2つの暗黙の経路のどちらかで届いていた。APIキーは推論サービスを呼び出すための認証情報である。推論サービスは、モデルをAPIで呼び出せるように提供するサービスである。

親がOpenHands CLIで、かつ隔離した作業ディレクトリで実行した場合だけ、2つの経路のどちらでもAPIキーが子エージェントに届かなかった。隔離した作業ディレクトリは、git worktreeで作った、リポジトリの別の作業ディレクトリである。この整理は研究の記録にある3つの条件を組み合わせたもので、研究の記録にこの形の文は無い。3つの条件は、節「実行記録を残した再実行による原因の切り分け」で説明する。

修正した後は、測定用のスクリプトが、APIキーをMCPサーバの設定のenv欄に書いて明示的に渡す。MCPサーバの設定のenv欄は、コーディングエージェントの設定ファイルの欄である。この欄には、MCPサーバごとに、起動するプロセスに渡す環境変数を書く。欄の名前は、OpenCodeではenvironment、残りの2つではenvである。

APIキーが必要なのに無いときは、並列実行のMCPサーバが子エージェントを起動する前に止まり、実行記録のファイルを失敗にする。実行記録のファイルは、並列実行のMCPサーバが1回の呼び出しごとに書くJSONのファイルである。起動した子エージェントの数と、完了した子エージェントの数と、状態の欄を持つ。

修正の前と後の経路を、次の図に示す。

修正の前
  測定用のスクリプトを起動したシェル (APIキーを読み込んである)
    └ 親エージェント
        └ 並列実行のMCPサーバ
            │  経路1: 環境変数の引き継ぎ
            │         親がOpenHands CLIのときは届かなかった
            │  経路2: リポジトリの最上位の.envファイルを読む
            │         隔離した作業ディレクトリには.envファイルが無い
            └ 子エージェント (APIキーの値は空の文字列)

修正の後
  測定用のスクリプト
    └ MCPサーバの設定のenv欄にAPIキーを書く
        └ 並列実行のMCPサーバ
            ├ APIキーがある: 子エージェントを起動する
            └ APIキーが必要なのに無い: 起動する前に止まり、実行記録のファイルを失敗にする

比較の測定の条件と、親エージェントごとの子エージェントの完了の数

この節では、比較の測定の条件と、親エージェントごとの結果を表で示す。

比較の測定の条件は次のとおりである。

次の表は、並列実行のMCPサーバの呼び出しが起きた実行を、親エージェントごとにまとめたものである。

親エージェント 呼び出しが起きた実行 実行記録のファイル 起動した子エージェント 完了した子エージェント
Qwen Codeの親 3回 実行ごとに1件、1件、2件 実行ごとに8件 実行ごとに8件、8件、7件
OpenCodeの親 1回 3件 合計16件 合計8件
OpenHands CLIの親 3回 合計4件 合計32件 合計0件

親がOpenHands CLIの3回の実行では、起動した子エージェント32件のうち、完了は0件だった。親がOpenCodeの実行では、親エージェントが編成のスクリプトの誤りを自分で修正して、実行し直した。編成のスクリプトは、並列実行のMCPサーバが実行する、子エージェントの起動の手順を書いたスクリプトである。

親がOpenHands CLIの3回の実行でも、8つの課題はすべて合格し、テストのファイルの書き換えは0件だった。記録からは、子エージェントが完了しなかった分を親エージェントが自分で解いたと読める。ただし、研究の記録に親エージェントの作業の内訳は無い。

子エージェントがすべて失敗しても結果に表れなかった判定の作り

この節では、子エージェントがすべて失敗した結果が、比較の結果として扱われ続けた仕組みを説明する。

測定用のスクリプトは、並列実行のMCPサーバが呼び出されたかを、実行記録のファイルがあるかどうかで判定した。実行記録のファイルは並列実行のMCPサーバのプロセスだけが書くので、呼び出しが無ければ作られない。

測定用のスクリプトは、子エージェントの起動の数と完了の数を、実行記録のファイルの欄を合計して記録した。課題の合否は、テストのファイルを元に戻してから各課題のテストを実行し、合格した課題の数で記録した。

子エージェントがすべて失敗しても、次の3つの理由で、失敗は結果に表れなかった。

親がOpenHands CLIで子エージェントの完了が0件だった結果は、約1日のあいだ、比較の測定の結果として扱われた。

実行記録を残した再実行による原因の切り分け

この節では、原因をどう切り分けたかを、作業の順に説明する。研究の記録では、AIエージェントが実装し、測定し、PRに対応した。このAIエージェントは、PRの差分を調べるレビュー用のAIエージェントとは別に動く。

AIエージェントは、親がOpenHands CLIの条件を1回だけ再実行した。このときは、実行記録のファイルを置くディレクトリを、実行の後も消さずに残した。取り出した記録では、8件の子エージェントがすべて、起動からおよそ1.2秒で終了していた。終了の理由は、認証の方式が選ばれていないという内容のエラーだった。

このエラーの文は、バージョン0.21.13のQwen Codeの、非対話モードの認証を調べるファイルにある。Qwen Codeは、非対話モードで起動したとき、認証の方式を、コマンドの引数と環境変数と設定ファイルから決める。どれからも決まらなければ、このエラーで止まる。

原因は次の3つの条件が重なったことだった。

  1. 親がOpenHands CLIのときは、親プロセスの環境変数が、並列実行のMCPサーバのプロセスに届かなかった。親がQwen CodeかOpenCodeのときは届いたので、シェルに読み込んだAPIキーが子エージェントまで届いていた
  2. 並列実行のMCPサーバは、環境変数にAPIキーが無いとき、自分のリポジトリの最上位のディレクトリの.envファイルを読む作りだった。比較の測定は隔離した作業ディレクトリで実行しており、そのディレクトリに.envファイルは無かった
  3. 測定用のスクリプトは、APIキーをMCPサーバの設定のenv欄に書いていなかった

並列実行のMCPサーバは、子エージェントを起動するとき、APIキーの値が取れなければ、空の文字列を環境変数に入れていた。

それ以前にも、親がOpenHands CLIの別の測定があった。この測定では、並列実行のMCPサーバを明示して呼び出させ、子エージェント1件が完了していた。研究の記録には、このときは主たる作業ディレクトリで実行して.envファイルが読まれた、という説明が最もよく合うと書かれている。どちらの作業ディレクトリで実行したかの記録は無いので、この説明は確定していない。

親プロセスの環境変数がMCPサーバに届くかどうかの実装ごとの違い

この節では、親プロセスの環境変数がstdio形式のMCPサーバに届くかどうかを、公開のソースコードで実装ごとに確かめた結果を示す。確認日は2026-09-29である。

次の表は、stdio形式のMCPサーバを起動するときに、各実装がMCPサーバのプロセスに渡す環境変数をまとめたものである。

実装 MCPサーバのプロセスに渡す環境変数 設定のenv欄の扱い 出典
MCPの公式Python SDK 既定では、引き継いで安全な環境変数の一覧だけである。POSIXではHOME、LOGNAME、PATH、SHELL、TERM、USERの6つである 6つの上に重ねて渡す stdioクライアントのソースコード
MCPの公式TypeScript SDK POSIXでは、Python SDKと同じ6つである 一覧の上に重ねて渡す stdioクライアントのソースコード
バージョン0.21.13のQwen Code 親プロセスの環境変数の全部である。この製品のデーモンと、その子プロセスの認証に使う3つの環境変数だけを除く 全部の上に重ねて渡す MCPクライアントのソースコード
バージョン1.18.18のOpenCode 親プロセスの環境変数の全体である environment欄を全体の上に重ねて渡す MCPの処理のソースコード
バージョン1.16.0のOpenHands CLI 依存の経路を読むと、MCPの公式Python SDKの既定の6つである 設定のenv欄をMCPの公式Python SDKの起動の引数に渡す 依存の定義、openhands-sdkのMCPの処理、fastmcpのstdioの処理

各行の補足は次のとおりである。

OpenHands CLIの行は、MCPの部分のソースコードを読んで得た説明である。この説明は、親がOpenHands CLIのとき環境変数が届かなかった観測に、最もよく合う。ただし、測定のときに入っていたfastmcpとMCPの公式Python SDKの正確なバージョンは確かめていない。そのため、この説明は、測定の環境で経路の全体を確かめた結果ではない。観測した事実は、この測定の起動の仕方に限られる。親がバージョン1.16.0のOpenHands CLIのとき、親プロセスの環境変数はMCPサーバに届かなかった。

3つのコーディングエージェントの公式ドキュメントには、環境変数を渡す欄の説明がある。確認したページは次の3つである。

一方で、3つのページのどれにも、起動したMCPサーバが親の環境の全体を受け取るかどうかの記述は無い。引き継ぎの挙動はソースコードの実装でだけ決まっていて、バージョンで変わりうる。

どちらの作りにも理由がある。公式のSDKの既定は、引き継ぐ環境変数を絞って、秘密の値の漏えいを避ける。Qwen Codeは、除く対象を製品の内部用の値に限っている。ソースコードのコメントには、利用者がシェルで実行するコマンドが第三者の認証情報の引き継ぎに依存する、という理由が書かれている。バージョン0.21.13のQwen Codeは、MCPサーバを起動するときも、同じ関数だけを通して親の環境変数を渡す。修正した後の測定用のスクリプトは、どちらの作りにも依存しない。測定用のスクリプトが、APIキーをMCPサーバの設定のenv欄に書いて明示的に渡すためである。

MCPサーバの設定のenv欄でAPIキーを明示的に渡す修正と測り直し

この節では、APIキーの渡し方の修正と、修正した後の測り直しの結果を示す。

AIエージェントは測定用のスクリプトを修正した。修正した後の測定用のスクリプトは、子エージェントが使うAPIキーを、MCPサーバの設定のenv欄に書いて明示的に渡す。AIエージェントは、並列実行のMCPサーバを明示して呼び出させる別の測定用のスクリプトも、同じ形に修正した。env欄に書いた値が3つのコーディングエージェントのすべてでMCPサーバに渡ることは、それ以前に別の値の受け渡しで確かめてあった。

AIエージェントは、OpenHands CLIのmcp.jsonにAPIキーが書かれることを、単体テストで確かめる形にした。その後、単体テストを3つのコーディングエージェントのすべてに広げた。

次の例は、MCPサーバの設定のenv欄にAPIキーを書く形を、公式ドキュメントの形に合わせて書き起こしたものである。サーバの名前と環境変数の名前と値は仮のものである。1つ目はQwen Codeの形で、OpenHands CLIのmcp.jsonも同じ形である。2つ目はOpenCodeの形である。

{
  "mcpServers": {
    "parallel-runner": {
      "command": "python",
      "args": ["-m", "example_parallel_runner"],
      "env": {
        "EXAMPLE_API_KEY": "replace-with-your-api-key"
      }
    }
  }
}
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "parallel-runner": {
      "type": "local",
      "command": ["python", "-m", "example_parallel_runner"],
      "environment": {
        "EXAMPLE_API_KEY": "replace-with-your-api-key"
      }
    }
  }
}

AIエージェントは、修正した後、親がOpenHands CLIの2つの条件を、1回ずつ測り直した。2つの条件は、エージェント向けの指示ファイル一式を置くかどうかで違う。エージェント向けの指示ファイル一式は、作業ディレクトリに置くAGENTS.mdと、スキルのファイルと、メモリのファイルの組である。次の表は、2つの条件で測り直した結果である。

条件 起動した子エージェント 完了した子エージェント 合格した課題 所要時間
指示ファイル一式を置く 8件 8件 8つのうち8つ 556.1秒
指示ファイル一式を置かない 合計16件 合計1件 8つのうち8つ 761.1秒

時間の上限は900秒だった。指示ファイル一式を置いた条件では、親エージェントが自分から並列実行のMCPサーバを呼び出した。子エージェント8件はすべて完了した。この実行は、親がOpenHands CLIの条件で、親エージェントが自分から並列実行のMCPサーバを呼び出し、起動した子エージェントがすべて完了した初めての実行だった。

指示ファイル一式を置かない条件でも呼び出しは起き、実行記録のファイルは3件だった。この条件で起動した16件のうち15件は、APIキーとは別の理由で完了しなかった。研究の記録には、この15件に、編成のスクリプトの誤りを修正して実行し直す型を含むと書かれている。この15件の原因の観測を続けることも、研究の記録に書かれている。

AIエージェントはこの結果から、次のように結論した。子エージェントの完了が0件だった原因は、コーディングエージェントの能力の差ではなく、測定用のスクリプトの作りにあった。AIエージェントは、レビュー用のAIエージェントの指摘を受けて、結論の範囲を書き直した。レビュー用のAIエージェントは、PRを作った後に、実装したエージェントとは別に起動するAIエージェントである。差分を読み取り専用で調べる。AIエージェントは、書き直した結論で、APIキーによって子エージェントがすべて失敗した問題が解消したと言い切った。残る15件の未完了は、APIキー以外の理由として分けた。

測り直しの2回の実行は、原因の切り分けのための追加の測定として、比較の集計とは別に記録した。測り直しは各条件で1回ずつだった。そのため、親がOpenHands CLIのときに、起動した子エージェントがすべて完了する頻度は、この記録からは言えない。

失敗を隠さない仕組みと、同じ取り残しを防ぐ単体テスト

この節では、APIキーの修正の後のPRで追加した、失敗を隠さない仕組みと、同じ取り残しを防ぐ単体テストを説明する。

AIエージェントは、APIキーが必要なのに無いときの処理を追加した。この場合、並列実行のMCPサーバは子エージェントを起動する前に止まり、実行記録のファイルを失敗にする。止まったことを子エージェントの1件の失敗として扱わず、呼び出しの全体を失敗にする。

このPRのレビューは3回あった。

  1. 初回のレビューで、レビュー用のAIエージェントは問題を実際に動かして示した。問題は、止める処理の例外が子エージェントの1件の失敗として扱われ、実行記録のファイルの状態が正常のまま残ることだった
  2. 2回目のレビューで、レビュー用のAIエージェントは、並列と直列の編成の書き方では、同じ例外がまだ正常のまま扱われることを示した
  3. 3回目のレビューを受けて、AIエージェントは、止める処理が一度動いたら実行記録のファイルに失敗の印を残す形にした。編成のスクリプトが例外を受け止めて握りつぶしても、この印は残る

AIエージェントは、同じPRで、APIキーが空のときに、空の文字列で呼び出し側の環境変数を上書きしていた処理をやめた。修正した後の並列実行のMCPサーバは、APIキーが無いとき、呼び出し側の環境にある認証を、そのまま子エージェントに渡す。

AIエージェントは、同じPRで、測定用のスクリプトが記録する行に、測定の側に不備があったことを示す印を自動で付ける処理も追加した。この印が付いた行は、エージェントの能力の観測として集計に数えない。印を付ける条件は、課題の実行が次の3つをすべて満たす場合である。

条件を狭くした理由は、能力の不足による失敗を測定の側の不備として集計から外すと、集計が実力より良く見えることである。この印を付ける処理は、並列実行のMCPサーバの呼び出しを判定する処理とは別の処理である。

同じ型の取り残しは、別の環境変数でも起きた。子エージェントのQwen Codeには、隔離したディレクトリを環境変数で渡す。このディレクトリは、利用者のホームディレクトリの代わりに使う。測定用のスクリプトは、親がQwen Codeのときだけ、親を起動する時点でこの値を設定していた。そのため、この値は、親がQwen Codeのときだけ、環境変数の引き継ぎで子エージェントまで届いた。親が残りの2つのときは、親を起動する時点でこの値が無いので、子エージェントに届かなかった。

AIエージェントは、この値もMCPサーバの設定のenv欄に書き、3つのコーディングエージェントのすべてに明示的に渡す形にした。この形は単体テストで確かめた。レビュー用のAIエージェントは、同じ取り残しがある2か所を指摘した。2か所は、別の測定用のスクリプトと、結合テストだった。AIエージェントは2か所とも同じ形に修正した。

AIエージェントは、その後のPRで、MCPサーバの設定のenv欄を組み立てる処理を、1つの関数にまとめた。呼び出し側が別々に値を設定すると、登録する場所が増えたときに、同じ取り残しが起きるためである。単体テストは、env欄に入る環境変数の名前の集合が、決めた集合と完全に一致することを確かめる。新しい認証情報が気づかれずに設定ファイルに書かれると、この単体テストが失敗する。

APIキーをenv欄に書いたことで追加した保護

この節では、APIキーを明示的に渡す修正によって生じた新しいリスクと、同じPRで追加した保護を説明する。

修正の後は、env欄に書いたAPIキーが、作業ディレクトリの中の設定ファイルに値のまま書かれる。レビュー用のAIエージェントは、このPRに7件の指摘を返した。指摘には、Critical、High、Medium、Lowの4段階の重大度が付く。7件の重大度はすべてMediumだった。AIエージェントは、7件のうちAPIキーの保護に関わる指摘を受けて、次の2つを修正した。

比較の測定を設計するときの2つの規則

この節では、この記録から一般化できる、比較の測定を設計するときの規則を2つ示す。研究の記録に、この形の文は無い。

  1. 比較の測定を始める前に、コーディングエージェントごとに違う暗黙の挙動を洗い出す。親プロセスの環境変数がMCPサーバに届くかどうかは、その挙動の1つである
  2. 認証情報を環境変数の引き継ぎに任せず、測定用のスクリプトから明示的に渡す

規則1の根拠は、親がOpenHands CLIのときだけ環境変数が届かなかった観測と、引き継ぐ範囲が実装ごとに違うソースコードである。規則2の根拠は、env欄で明示的に渡す修正と測り直しの節と、失敗を隠さない仕組みの節に書いた事実である。

補足として、外部の評価の実行基盤でも、同じ型の失敗が起きた。AIエージェントは、外部の評価の実行基盤で、Qwen CodeをACPで動かした。ACPは、エディタとコーディングエージェントをつなぐための公開の手順である。この実行は、Qwen Codeの起動とACPの接続までは進み、認証の段階でエラーになった。課題の得点は0だった。AIエージェントは、その時点の実行基盤を起動する処理の中に、モデルの認証の情報をコンテナに渡す設定の項目を見つけられなかった。研究の記録では、これを原因としている。同じリポジトリの測定用のスクリプトは、環境変数と設定ファイルの写しを渡して、同じ認証を通していた。AIエージェントは、この実行を能力の観測でなく、測定の側の不備として記録した。

親プロセスの環境変数の引き継ぎの挙動は、公式ドキュメントでなく、使っているバージョンのソースコードで確かめられる。読む箇所は、stdio形式のMCPサーバを起動する処理である。その処理が親の環境の全体を渡すか、一覧で絞るかを確かめてから、そのバージョンのコーディングエージェントを比較の測定に使うとよい。

参考にした資料

本文で参照した公開の資料は次のとおりである。いずれも2026-09-29に確認した。