AI の自動実行が CI の結果を読めなかった:GitHub のトークン権限を書き込み用と読み取り用に分けて 3 回直した構築ログ

AI が作った PR の CI 結果を待つ手順が 15 分で時間切れになりました。GitHub App のトークンと workflow の GITHUB_TOKEN を書き込み用と読み取り用に分け、3 回の修正で必要な読み取り権限だけを足した記録です。

19分で読めます Data
灰色の大きな鍵 1 本が、書き込み用の濃い青の鍵と読み取り用の淡い青の鍵の 2 本に分かれ、それぞれ別の扉に向かっているイラスト
この記事は AI が下書きを補助し、運営者が内容を確認して公開しています。

AI エージェントに PR(プルリクエスト:変更の取り込みを依頼する GitHub の仕組み)の作成や検査結果の確認を任せる自動化を作っている方向けの構築ログです。筆者がこのサイトの運用で試したところ、CI(コードを自動でチェックする仕組み)の結果を読む権限が足りず、修正を 3 回重ねてようやく動きました。読み終わると、権限エラーが出たときにどのトークンのどの権限を疑えばよいかを切り分け、書き込みの権限を広げずに読み取りだけを足せるようになります。

この記事で分かること

  • GitHub App(GitHub に登録して使う、権限を決めた自動化用のアプリ)のトークン(API を呼ぶときに見せる、合言葉のような文字列)と、workflow(GitHub Actions で自動実行する処理の設定ファイル)の GITHUB_TOKEN を、書き込み用と読み取り用に分ける考え方
  • 「Resource not accessible by integration」というエラーが出たとき、どこを見て切り分けるか
  • gh pr checks を動かすために、筆者の環境で必要だった読み取り権限

ざっくり言うと:権限の修正は 3 回必要で、分けると最小の権限で済んだ

CI の結果を読めなかった原因は、読み取りに使ったトークンの権限不足でした。

最初は書き込み用の GitHub App のトークンに読み取り権限を足そうとしましたが、App 自体がその権限を持たず付きませんでした。そこで読み取りだけを workflow の GITHUB_TOKEN に分け、最後に actions: read を足して動きました。

筆者が修正前後の権限を数えたところ、書き込み用の App の権限の増減は 0 個で、読み取り用の GITHUB_TOKEN に read 権限 5 個を割り当てた形になりました。

構成:AI が PR を作り、CI の結果を待って公開する流れ

筆者のサイトでは、記事の公開を GitHub Actions(GitHub 上で処理を自動実行する仕組み)で回しています。流れは次のとおりです。

AI の手順:記事を作り、push(手元の変更を GitHub へ送ること)して PR を作る(App トークンで書き込み)
         ↓
CI が PR を検査する
         ↓
wait-checks.sh が gh pr checks で CI の結果を待つ(ここで読み取り権限が要る)
         ↓
全部合格なら Merge(PR の変更を本流のブランチに取り込むこと)して公開する

トークンは 2 種類です。

トークンどう作られるか筆者の環境での役割
GitHub App のインストールアクセストークン(App を入れたリポジトリでだけ使える、期限付きのトークン)作成した GitHub App から workflow の中で発行するpush・PR 作成・Merge などの書き込み
GITHUB_TOKENworkflow の実行ごとに GitHub が自動で用意する2 回目の修正以降、CI の結果の読み取り

GitHub の公式ドキュメントは、GITHUB_TOKEN に無い権限が必要なときの方法として、GitHub App を作って workflow 内でインストールアクセストークンを発行するやり方を挙げています(Automatic token authentication)。筆者の環境もこの形でした。

なぜ権限の分け方が重要か

AI に任せる手順は、何をするかを人が 1 行ずつ決めていません。だから、その手順が持つ権限は狭い方が安全です。

公式も、同じページで GITHUB_TOKEN には必要最小限の権限だけを与えるよう勧めています。GitHub App についても、既定では権限を持たず、登録時に選んだ権限だけが使えると説明されています(Choosing permissions for a GitHub App)。

読み取りのために書き込み用トークンへ権限を足すと、AI の手順が触れる範囲も広がります。読み取りを別のトークンに分けたのは、これを避けるためです。

つまずき 1:checks: read を要求したが App が持っていなかった

発端は、自動で作った PR の CI が全部合格していたのに、待ち処理(wait-checks.sh)が 15 分待って時間切れになり、PR を閉じてしまったことでした。

調べると、App トークンの権限は contents・pull-requests・issues の 3 つだけで、gh pr checks(PR の検査結果を一覧で見る gh コマンド)が権限不足で失敗していました。しかも当初の wait-checks.sh は、gh pr checks の失敗メッセージ(stderr:エラー用の出力)を捨てて「まだ待ち中」と扱っていました。そのため、原因が分からないまま 15 分が過ぎていたのです。

1 回目の修正では、次の 2 つを行いました。

  1. App トークンを発行するときに checks: read と statuses: read を要求する(読み取りだけ)
  2. wait-checks.sh が失敗したとき、エラーの先頭 300 文字を表示してから待つようにする

ところが、次の実行で gh pr checks は「GraphQL: Resource not accessible by integration」というエラーで失敗しました。GraphQL は、GitHub のデータを読み書きする API(プログラムから操作するための窓口)の一種です。原因は、GitHub App 自体が checks の権限を持っていないことでした。筆者の環境では、App が持たない権限は、発行時に要求しても付きませんでした。

公式ドキュメントにも、インストールアクセストークンでの API の成否は App の権限だけで決まると書かれています(Choosing permissions for a GitHub App)。また、チェック実行(check run)を取得する API には、Checks のリポジトリ権限の read が必要です(REST API endpoints for check runs)。

つまずき 2:読み取りを GITHUB_TOKEN に切り替え、書き込みは App のまま分けた

App の権限を変えるのは管理者の設定変更なので、ここでは変えず、CI の結果を読む部分だけを workflow の GITHUB_TOKEN に分けました。

操作使うトークン
書き込み(push・PR 作成・Merge)今までどおり GitHub App のトークン(GH_TOKEN)
CI の結果を読む(gh pr checks)publish ジョブ(workflow の中の 1 つの処理のまとまり。ここでは公開を担当する)の GITHUB_TOKEN(CHECKS_GH_TOKEN という名前で渡す)

publish ジョブは AI を動かさないので、ここに読み取り権限を足しても AI が触れる範囲は広がりません。

workflow 全体の既定権限は contents: read だけにしておき、publish ジョブにだけ読み取り権限を足しました。公式によると、workflow の permissions キーで GITHUB_TOKEN の権限を workflow 全体またはジョブ単位で変えられます(前出の Automatic token authentication)。

次は、3 回目の修正まで終えた筆者の最終的な設定です。2 回目の修正の時点では actions: read の行がまだ無く、つまずき 3 のエラーで失敗しました。コピーして使う場合は、actions: read まで含めてください。

permissions:
  contents: read

jobs:
  publish:
    permissions:
      contents: read
      checks: read
      statuses: read
      pull-requests: read
      actions: read # 3 回目の修正で追加。2 回目の時点ではこの行が無く失敗した

あわせて wait-checks.sh と release-to-main.sh を、CHECKS_GH_TOKEN があればそれで gh pr checks を呼び、無ければ従来の GH_TOKEN を使う形にしました。gh(GitHub CLI:GitHub をコマンドで操作する公式ツール)は、GITHUB_TOKEN を GH_TOKEN として渡すと使えます(Automatic token authentication)。

図 1 のとおり、分けた後は AI の手順が持つトークンと、CI の結果を読むトークンが別々になります。

分ける前は 1 つの App トークンで書き込みと CI 結果の読み取りを行い checks の読み取りで失敗、分けた後は書き込みを App トークン、読み取りを publish ジョブの GITHUB_TOKEN が担う構造図

つまずき 3:gh pr checks は workflow の実行も読むので actions: read が要った

2 回目の修正の後も「GraphQL: Resource not accessible by integration (…checkSuite.workflowRun)」というエラーが出ました。

エラーの末尾にある checkSuite.workflowRun が手がかりでした。gh pr checks は、各 check がどの workflow の実行によるものかも読みます。そのため、checks の権限に加えて actions: read が要りました。

3 回目の修正は、publish ジョブの権限に actions: read を 1 つ足すだけの 2 行の変更です。最終的な設定は、つまずき 2 の YAML の例に載せています。

注意したいのは、この「gh pr checks に actions: read が要る」という点が、gh の公式マニュアルの gh pr checks のページには書かれていないことです(gh pr checks)。筆者がこのサイトの運用で、エラーの文言から確認した記録です。

3 回の修正で、それぞれ何が分かって次に進んだのかを時系列にしたのが図 2 です。

15 分の時間切れから、1 回目でエラー表示を追加、2 回目で読み取りを GITHUB_TOKEN に分離、3 回目で actions: read を追加するまでの時系列図

権限の対応表:要求した・持っていた・足りなかった

筆者が各回の記録から、要求した権限の集合と持っていた権限の集合を比べ、差を取りました。結果は次の表と図 3 のとおりです。

回読み取りに使ったトークン要求した権限持っていた権限足りなかった権限
1 回目App トークンchecks: read、statuses: readcontents、pull-requests、issueschecks: read(statuses は記録が無く不明)
2 回目publish ジョブの GITHUB_TOKENcontents・checks・statuses・pull-requests をすべて read要求した 4 つactions: read(実行して判明)
3 回目publish ジョブの GITHUB_TOKEN上の 4 つ + actions: read要求した 5 つなし

3 回の修正ごとに 5 つの権限の過不足を示し、1 回目は checks、2 回目は actions が不足、3 回目はすべてそろったことを示す対応表

足りなかった権限は、1 回目が checks、2 回目が actions と、毎回 1 つずつ見つかりました。どちらも、表示されるようになったエラーの文言から特定できました。

3 回の修正 PR は、すべて 2026-10-28 のうちに merge されました。筆者が merge 時刻(UTC 17:33・18:17・18:44)から間隔を計算した値が次の表と図 4 です。

区間分
1 回目 → 2 回目の merge44
2 回目 → 3 回目の merge26.9
1 回目 → 3 回目の merge70.9

修正 PR の merge の間隔を示す横棒グラフ。1 回目から 2 回目の merge まで 44 分、2 回目から 3 回目まで 26.9 分、1 回目から 3 回目の merge まで 70.9 分

1 回目の修正の merge から 3 回目の merge までは 70.9 分でした。この値は 1 回目の修正そのものにかかった時間を含みません。修正後に CI の結果を読めるまでの分数は、記録から確認できなかったため載せていません。

エラーを捨てない:wait-checks.sh を直した理由

今回いちばん効いたのは、失敗の理由を表示するようにした変更だと筆者は考えています。

設計gh pr checks が失敗したとき結果
当初エラーを捨てて「待ち中」と扱うCI は合格していたのに、原因不明のまま 15 分で時間切れ
1 回目の修正以降エラーの先頭 300 文字を表示してから待つエラーの文言から、2 回目・3 回目の不足を特定できた

待ち処理が「失敗」と「まだ終わっていない」を区別しないと、権限の不足は時間切れに化けます。CI が遅いせいにも見えるため、権限を疑うきっかけがありません。

自分の環境で確かめる手順

権限エラーで止まったときは、次の順に確かめると切り分けやすいと筆者は考えています。

  1. 待ち処理の中で、エラー出力を捨てていないかを確認する
  2. エラーの文言を読み、REST API(GitHub の API のもう 1 つの種類。URL ごとに操作が決まっている)か GraphQL かを見分ける
  3. 失敗したコマンドが、App トークンと GITHUB_TOKEN のどちらで動いているかを確かめる
  4. App トークンなら、App 自体にその権限があるかを管理者に確認する
  5. GITHUB_TOKEN なら、ジョブの permissions に読み取り権限を足す

公式によると、REST API で権限が足りないと 403 が返り、X-Accepted-GitHub-Permissions ヘッダーで必要な権限が分かります。GraphQL で権限が足りない場合は 401 が返るとも書かれています(Choosing permissions for a GitHub App)。

筆者の環境で出た「Resource not accessible by integration」は gh pr checks の表示した文言で、公式の 401 と同じ場面かは確かめていません。

つまずきやすい点

つまずき起きること確認先
App が持たない権限を、トークンの発行時に要求する要求しても付かず、同じエラーが続くApp の登録設定(管理者)
App の権限を変えたのに反映されないインストール先の所有者が承認するまで、従来の権限のままインストール先アカウントの承認画面
checks の権限だけを足すgh pr checks は workflow の実行も読むため、まだ失敗する(筆者の運用で確認)エラー末尾の workflowRun

App の権限を変えた場合の動きは、公式に書かれています。インストール先アカウントの所有者に新しい権限の承認が求められ、承認されなければ従来の権限のまま使われます(前出の Choosing permissions for a GitHub App)。そのため、App 側の修正は自分だけで完結しないことがあります。

Checks API の書き込み権限は GitHub App だけが持てる、と公式は書いています(REST API endpoints for check runs)。今回は読み取りだけなので GITHUB_TOKEN で足りましたが、check を書き込む自動化には使えません。

GITHUB_TOKEN の既定の権限がリポジトリの設定で変わるか、fork(他人がリポジトリを複製したもの)から来た PR で権限が変わるかは、今回参照した 4 つの公式ページの本文では確認できなかったため扱いません。自分の環境では、リポジトリの設定画面と公式ドキュメントで確かめてください。

まとめ

  • 書き込みは GitHub App のトークン、CI の結果の読み取りは AI を動かさないジョブの GITHUB_TOKEN、と分けると、App の権限を 1 つも増やさずに済んだ
  • gh pr checks には checks の読み取りに加えて actions: read が要った(gh の公式マニュアルには無く、筆者の運用で確認)
  • 待ち処理がエラーを捨てると、権限不足は時間切れに化ける。失敗の理由を表示するだけで、残り 2 回の切り分けができた

自動化の権限設計や、AI に任せる手順の分け方を相談したい方は、お問い合わせからご連絡ください。AI が作った集計の SQL が正しいか不安な場合は、BigQuery SQL レビューパックも用意しています。

参考にした一次情報

公式ページの記述は 2026-11-06 時点で確認した内容です。