AIエージェント開発・運用の事故録 #05
PR番号ではなく、
Exact SHAを信じる
検証したコードと操作対象を一致させる
公開日:2026年8月25日 著者:飯田 友広(Tomohiro Iida)
この記事の結論
AIエージェント運用では、PR番号ではなく40桁のcommit SHAを操作対象の識別子として扱います。
PR head、CI検証対象、レビュー対象、Owner承認、merge後、デプロイ、本番配信の7つのSHAへ別々の名前を与えます。
PR headは2経路から独立に取得して突き合わせ、UNKNOWNはDENYとして扱います。
squash merge後のSHAはPR headと別物なので、PR側のexact-head CIをそのまま流用しません。
AIエージェントへ実装を任せると、報告は自然言語で返ってきます。「CIはGREENです」「レビュー済みです」「mainへ入っています」。どれも文章としては正しく読めます。
一方でGitHubの側には、PR head、CIが実際に検査したcommit、レビューが読んだ差分、Ownerが承認したcommit、merge後にmainへ生成されたcommit、デプロイが使ったcommit、本番がいま配信しているcommitという、別々のcommitになりうる7つの状態が並んでいます。
会話の中では1つに見えるものが、システムの中では7つあります。ここがずれたまま操作すると、検証済みのコードと操作対象が別物になります。
結論を先に書きます。
AIエージェント運用では、PR番号ではなく40桁のcommit SHAを操作対象の識別子として扱います。
PR番号は可変参照です。同じ番号のまま中身が入れ替わります。SHAは内容へ固定された識別子です。検証、承認、merge、デプロイ、本番確認は、すべて同じSHAを指しているかどうかで結び付けます。
Incident Card
INCIDENT:
remote branchには新しいcommitが載っていたのに、GitHubのPull Request APIが古いheadを返し続け、Controllerがその古いheadをmergeした
SYMPTOM:
検証済みの修正3件(既知High 2件を含む)を取りこぼした状態がmainへ入った
FALSE_ASSUMPTION:
PR番号とCIのGREEN表示が一致していれば、merge対象は検証済みのコードと同じである
ROOT_CAUSE:
検証したSHAと操作するSHAを別々に取得して突き合わせる工程が無く、単一のPR head表示を信じていた
IMMEDIATE_FIX:
merge直前にremote branch refを別経路で取り直し、PR APIのheadと一致しなければmergeしない
SYSTEM_FIX:
PR_HEAD_SHAからPRODUCTION_SHAまで7つのSHAへ別々の名前を与え、等式が崩れたらUNKNOWNをDENYとして扱う
REMAINING_RISK:
squash merge後のSHAはPR headと別物であり、PR側のexact-head CIをそのまま流用できないPR番号は、中身が入れ替わる可変参照である
Pull Requestの番号は作業の入れ物です。中身であるcommitは、push、rebase、force push、base branchの前進によって変わります。番号は変わりません。
そのため次の文はいずれも、単独では検証対象を特定できません。
- 「PR #1234はCIがGREENです」
- 「PR #1234はレビュー済みです」
- 「PR #1234をmergeしました」
どれも「いつの#1234か」を含んでいません。AIエージェントが複数走り、mainが数十分単位で進む環境では、この欠落が判断を壊します。
2026-08-17、Netsujoのリポジトリで実際に次が起きました。remote branchには3つのcommitがpush済みだったのに、GitHubのPull Request APIが古いheadを返し続けました。Controllerはその古いheadをmergeし、検証済みの修正3件を取りこぼした状態がmainへ入りました。
このときmerge APIのsha preconditionは役に立ちませんでした。preconditionが比較するのはGitHub自身が持つPR headであって、remote branchの実refではありません。PR headが古ければ、古い値どうしで一致します。
誤った前提:CIがGREENなら、merge対象は検証済みである
「CIがGREEN」という表示は、何かのcommitに対して検査が通ったことしか示しません。示していないのは次です。
- どのcommitを対象に走ったのか
- そのcommitがいまのPR headと同じか
- required checkだったのか、任意のjobだったのか
- 変更内容によってskipされた検査は無かったか
- run完了後にPR headが進んでいないか
同じ問題は再実行にも出ます。GitHub Actionsのmanual rerunは、元のrunの古いevent payloadを再利用します。したがってrerunのGREENは、rerunした時刻ではなく元のevent時点のSHAに対する結果です。
Netsujoのリポジトリでは、この穴を検査で塞いでいます。.github/workflows/ci.ymlの先頭にはReject stale PR head rerunというstepがあり、eventのhead SHAとlive branch refをGit protocolで照合し、一致しなければSTALE_PR_HEAD=FAILで落とします。.github/workflows/pr-exact-head-ci.ymlは、local checkout、event head、remote branch refの3経路を突き合わせ、1つでも欠けたり食い違ったりすればFAILにします。
根本原因:同一性の確認が、どこにも実装されていなかった
事故の原因は、AIの判断力でも報告の言葉遣いでもありません。検証したSHAと操作するSHAを機械的に突き合わせる工程が、経路上に存在しなかったことです。
人間の運用でも同じ構造は起きます。ただしAIエージェントを複数並列で走らせると、mainが動く頻度と報告の量が増え、目視での照合が追いつかなくなります。
その場の対処:merge直前に、独立した経路でheadを取り直す
最初に入れた対処は単純です。merge直前に、GitHubのPR APIとは別のAPIからremote branch refを取り直し、突き合わせます。
scripts/agent-os/integration-controller.mjsのevaluateHeadIdentityが、この判定の正本です。
PR_API_HEAD_SHA = REMOTE_BRANCH_REF_SHA = REQUIRED_CI_TARGET_SHA = MERGE_EXPECTED_HEAD_SHAこの等式が成立しない場合の返り値は、すべてDENY側です。
| 状態 | code | 扱い |
|---|---|---|
| PR headが取れない | PR_HEAD_UNKNOWN | DENY |
| remote refが取れない | REMOTE_REF_UNKNOWN | DENY |
| PR headとremote refが違う | STALE_PR_HEAD | DENY |
| CI対象SHAが違う | CI_TARGET_MISMATCH | DENY |
| merge予定headが違う | MERGE_HEAD_MISMATCH | DENY |
重要なのは、取得できなかった場合もDENYにしていることです。UNKNOWNをALLOWへ倒すと、判定の欠落が自動承認へ化けます。
仕組みへの変換:7つのSHAへ、別々の名前を与える
同一性を確認するには、まず同一視をやめる必要があります。Netsujoでは次の7つを別々の変数として扱います。
| 名前 | 指しているもの | 取得元 |
|---|---|---|
| PR_HEAD_SHA | PRのheadが指すcommit | PR APIとremote branch refの両方 |
| CI_TESTED_SHA | CIが実際にcheckoutして検査したcommit | workflow runの対象ref |
| REVIEWED_SHA | レビューが読んだ差分のcommit | review submitted時のhead |
| OWNER_AUTHORIZED_SHA | Ownerがmergeを認可したcommit | PR本文のowner marker |
| MERGED_SHA | merge後にmainへ生成されたcommit | merge結果のcommit |
| DEPLOYED_SHA | デプロイがビルドへ使ったcommit | deploy workflowの実行対象 |
| PRODUCTION_SHA | 本番ドメインがいま配信しているcommit | 配信中deploymentのメタデータ |
この分離は、リポジトリ上で次のように実装されています。
- OWNER_AUTHORIZED_SHAは、PR本文へ残す認可行で表現します。対象のPR番号と40桁のcommit SHAを組で束縛する形式です。PR番号が違えば
DIFFERENT_PR_AUTHORIZATION、SHAが違えばAUTHORIZATION_SHA_MISMATCHになります。承認は「このPR」ではなく「このPRのこのcommit」に対して与えます。 - MERGED_SHAは、PR headとは別物です。squash mergeが作るcommitはPR headそのものではありませんし、その間にmainが動いていれば内容も変わります。そこで
.github/workflows/candidate-exact-sha-gate.ymlが、merge後の候補SHAをcheckoutしてlint、typecheck、test、buildを回し直し、そのSHAへcommit statusを書きます。 - PRODUCTION_SHAは、
scripts/deploy-policy/resolve-production-sha.mjsが本番aliasからdeploymentを辿って確定します。期待値は人の入力ではなくworkflowの実行対象commitを正本にしており、取得失敗、記録が空、食い違いはすべて失敗になります。
検証権限と記録権限を分ける
candidate-exact-sha-gate.ymlでは、候補コードを実行するjobにstatuses: writeを与えていません。statusを書くjobは候補コードを1行も実行しません。検証対象のコードとその依存は任意のコマンドを実行できるため、そこへ書き込み権限を置くと、判定結果そのものを偽造できてしまいます。
同一性の設計は、値の照合だけでは終わりません。その値を誰が書けるかまで含めて初めて成立します。
再利用できるExact SHA照合チェックリスト
自組織へ移すときは、次を順に確認します。
- 報告文へPR番号だけでなく40桁SHAを必須項目として入れているか
- PR headを2経路(PR APIとremote branch ref)から独立に取得しているか
- CI runが対象としたSHAを記録し、いまのPR headと突き合わせているか
- 再実行のGREENが、元eventの古いSHAに対する結果でないことを検査しているか
- 承認をPR単位ではなくSHA単位で記録しているか
- merge後SHAに対して、PR側のCIとは別に検証を回しているか
- デプロイ対象SHAと本番が配信中のSHAを突き合わせているか
- いずれかが取得できない場合をDENYにしているか
短縮SHAは避けます。判定はすべて40桁小文字で行い、形式が違う時点で落とします。
Evidence
| 内容 | 分類 | 根拠 |
|---|---|---|
| PR APIの古いheadをmergeし、検証済み修正3件を取りこぼした(2026-08-17) | OBSERVED | scripts/agent-os/integration-controller.mjsのevaluateHeadIdentityに事故経緯として記録 |
| merge対象の同一性を4値の等式で判定し、UNKNOWNをDENYにしている | IMPLEMENTED | scripts/agent-os/integration-controller.mjs |
| 承認をPR番号と40桁SHAの組で検証している | IMPLEMENTED | scripts/agent-os/integration-controller.mjsのownerMergeAuthorization |
| 再実行時にeventのhead SHAとlive branch refを照合し不一致で落とす | IMPLEMENTED | .github/workflows/ci.ymlのReject stale PR head rerun |
| PR headの素性をlocal checkout、event、remote refの3経路で確認する | IMPLEMENTED | .github/workflows/pr-exact-head-ci.yml |
| merge後の候補SHAをcheckoutし直してfull gateを回しstatusを書く | IMPLEMENTED | .github/workflows/candidate-exact-sha-gate.yml |
| 検証jobへstatus書込み権限を与えず、記録jobを分離している | IMPLEMENTED | .github/workflows/candidate-exact-sha-gate.yml |
| 本番配信中のcommitをaliasから辿って確定し、期待値との不一致を失敗にする | IMPLEMENTED | scripts/deploy-policy/resolve-production-sha.mjs |
| SHA同一性を機械で強制すると取り違えの発見が早くなる | INFERRED | 導入後の削減幅は未計測です |
適用限界
- ここで扱うのは、GitHubとGitHub Actionsを使う運用です。他のホスティングでは、同等の情報をどこから2経路で取れるかを個別に設計する必要があります。
- 全12回の事故録のうち、本記事はmerge前後の同一性までを扱います。デプロイ後の本番確認そのものは別の回で扱います。
- 7つのSHAをすべて機械で突き合わせる構成は、AIエージェントが並列でmergeを行う環境を前提にした当社の運用目安であり、変更頻度の低い小規模開発へそのまま持ち込むと管理コストが価値を上回る場合があります。
残るリスク
- squash mergeが作るcommitはPR headと別物なので、PR側のexact-head CIをそのまま流用できません。merge後SHAには専用のgateが要ります。
- commit statusがsuccessでも、それがどのworkflow runから書かれたかを確認しなければPASSとして扱えません。statusを書ける主体を限定する統制が別に必要です。
workflow_dispatchは選択したrefにある定義で実行されます。定義そのものを書き換えられる主体に対しては、workflow内のguardは防御になりません。- SHAを揃えても、検査の中身が薄ければ「検証済み」の意味は薄いままです。同一性は前提であって、品質そのものではありません。
まとめ
AIエージェントの報告は自然言語で届き、システムの状態は40桁の識別子で決まります。この2つを結び付ける鍵がExact SHAです。
PR番号 … 作業の入れ物。中身は入れ替わる
Exact SHA … 内容へ固定された識別子。検証と操作を結ぶ唯一の鍵PR番号で会話し、SHAで判断します。取得できない値はUNKNOWNとして扱い、UNKNOWNはALLOWにしません。
よくある質問
PR番号を使うこと自体が問題なのですか
問題ではありません。PR番号は人が作業を指すための便利な参照です。避けるべきは、PR番号だけを根拠に検証済みかどうかを判断することです。会話はPR番号、判定は40桁SHAと役割を分けます。
CIがGREENなら、そのままmergeしてよいのではありませんか
GREENはあるcommitに対する結果です。そのcommitがいまのPR headと同じか、required checkだったか、変更内容でskipされた検査が無かったかまでを確認して初めて、merge対象の検証済みを主張できます。
squash mergeのあとに、もう一度CIを回す必要はありますか
PR headとmerge後SHAは別物なので、PR側の結果をそのまま流用できません。Netsujoではmerge後の候補SHAをcheckoutしてlint、typecheck、test、buildを回し直し、そのSHAへstatusを残しています。
短縮SHAで運用してもよいですか
推奨しません。当社の運用では、判定に使う値をすべて40桁小文字に固定し、形式が違う時点で失敗させています。短縮形は人が読むための表示に限定します。
AIが「SHAは一致しています」と報告した場合、それを信じてよいですか
自己申告は証拠ではありません。値を取得した経路、対象のrun、判定コードが記録に残っているかを確認します。判定を行う側と、その結果を書き込む側の権限を分けておくことも同時に必要です。
この記事の著者

飯田 友広
代表取締役
Netsujo株式会社 代表取締役。京都発のWeb3・AI実装スタートアップを2023年6月に創業。Webサイトを営業基盤として捉え、経営・営業・検索・生成AI・コンバージョン・計測を横断して課題と改善優先順位を整理する「Netsujo SIGNAL」を設計・運営。京都ビッグデータ活用プラットフォーム参画(小規模企業会員・ベンチャー)、同プラットフォーム発のワーキンググループ「Chain Up KYOTO」参画(2026-03-10)、IVS2026サイドイベント「なぜ京都でWeb3.0ビジネスなのか」を京都府庁旧議場で開催(2026-07-02・Netsujoとして主催、企画・登壇・運営/京都府デジタル政策推進課は共催)。京都美術工芸大学での講義・龍谷大学でのセミナー実績、ITコミュニティ「みやこでIT」(connpassメンバー614名・イベント167件・2019年2月から運営)運営。NPO法人NEMTUS理事、BAR KRYPTO運営。ソーシャル企業認証「S認証」認証企業(2026年2月認証・2026年4月公表)。技術領域はWeb3/ブロックチェーン/DID/NFT/生成AI/コミュニティ運営。
プロフィールを見るこの記事が向いている方
AIエージェントへ実装を任せ、PRとCIの状態を人が読み直している開発責任者
merge、デプロイ、本番反映のどこで検証済みの前提が切れるかを整理したい方
承認とmergeをSHA単位で記録し、判定を機械へ移したいチーム
— 壁打ち相談
読者のよくある相談
記事を読んだ後に「自分の状況だとどう判断すべきか」を整理するための壁打ち相談を受け付けています。下記のような相談例が当てはまる方は、お気軽にご連絡ください。
Q. CIがGREENという報告だけでmergeしてよいか判断できない
CIが対象としたSHAといまのPR headを突き合わせ、一致しなければ止めます。
Q. squash merge後のコードを誰も検証していない
merge後の候補SHAをcheckoutし直し、そのSHAへstatusを残す経路を用意します。
Q. 承認したはずの内容と、実際にmergeされた内容が違う
承認をPR番号ではなくPR番号と40桁SHAの組で記録します。
上記いずれかが該当する場合、初回30分の壁打ち相談で論点整理に対応します。記事に書ききれない個別事情を踏まえた判断材料が必要な段階こそ、壁打ちが活きやすいフェーズです。