GitHub Projectsのdeployment statusが90日で消える変更と長期保存の実装方法

GitHub Projectsで90日より古いdeployment statusが取得できなくなったのは、APIの不具合や権限不足ではなく、GitHubが導入した新しい保持ポリシーによるものです。作成から90日を超えたdeployment statusは自動的に削除され、REST APIとGraphQL APIのどちらからも取得できません。一方、deploymentの現在の状態は維持されます。(The GitHub Blog)

そのため、デプロイの成功・失敗履歴を監査、障害分析、リリース評価に利用している組織は、90日以内に外部ストレージへ保存する仕組みが必要です。実運用では90日ごとではなく、日次または週次でエクスポートし、Webhookによるリアルタイム保存とAPIによる定期照合を組み合わせる方法が安全です。

なお、GitHubは今回の告知で、ストレージ削減や性能改善といった保持期間変更の詳しい理由を公表していません。原因を推測するよりも、クラウドサービス側の新しいデータ保持仕様として対応する必要があります。(The GitHub Blog)

目次

GitHubの90日保持ポリシーで何が変わったのか

2026年7月16日、GitHubはProjectsの高度な検索機能を一般提供すると発表しました。その「Other improvements」の中で、deployment statusに90日間の保持ポリシーを導入したことも案内しています。

変更内容を整理すると、次のとおりです。

データ90日経過後の扱い
deploymentの現在の状態今回の変更による影響なし
個々のdeployment status自動的に削除
REST APIのstatus一覧90日を超えたstatusは返らない
GraphQLのDeployment.statuses90日を超えたstatusは返らない
Projectsで参照する過去のstatus情報長期履歴の保存先としては利用できない
外部保存済みのJSONやデータベース自社の保持ポリシーに従って保存可能

重要なのは、現在のstateと過去のstatus履歴は別のデータだという点です。

deploymentの現在のstateがSUCCESSで残っていたとしても、過去にQUEUED、IN_PROGRESS、FAILURE、SUCCESSと遷移した時刻や説明、実行者、ログURLまで残るとは限りません。今回削除されるのは、こうした個々の状態遷移を表すdeployment statusです。(The GitHub Blog)

「deployment status」とProjectsの「Status」フィールドは別物

今回の変更で対象になるのは、デプロイ処理に関連付けられたdeployment statusです。GitHub Projectsで利用するカスタムフィールドの「Status」そのものが90日で削除されるという意味ではありません。

混同しやすいデータを整理すると、次のようになります。

用語主な用途今回の90日制限
Deployment statusデプロイの待機中、実行中、成功、失敗などを記録対象
Deployment statedeploymentの現在の状態今回の変更では維持
ProjectsのStatusフィールドBacklog、In progress、Doneなどの作業管理対象外
Pull Requestのreview state承認、変更要求などのレビュー状況対象外
reviews:フィルターProjects上でPRをレビュー状態により絞り込む対象外

同じ発表で追加されたreviews:フィルターは、Projects内のPull Requestアイテムをレビュー状態で絞り込むための検索条件です。Reviewersフィールドに記録されたレビュー依頼先と各ユーザーの最新レビューを基に動作しますが、deployment statusの保持期間や復元とは関係ありません。(The GitHub Blog)

対象サービスと互換性条件

今回の変更は、特定のSDKやGitHub CLIのバージョンだけに発生するものではありません。GitHub側でデータそのものが削除されるサーバーサイドの変更です。

利用方法影響対応
GitHub.com上のGitHub Projects影響ありProjectsを長期履歴の保存先にしない
REST API影響あり90日以内にstatusを取得して外部保存
GraphQL API影響ありDeployment.statusesをページングして外部保存
GitHub CLIのgh api影響あり内部的にRESTまたはGraphQLを使うため同じ結果になる
OctokitなどのSDK影響ありSDKを更新しても削除済みstatusは戻らない
REST APIのバージョン固定回避不可APIヘッダーを旧版にしても削除済みデータは復元できない
GitHub Enterprise Server告知だけでは判断不可利用中のGHESバージョン別リリースノートを確認

REST APIでは、次のエンドポイントでdeployment statusを取得します。

GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses

GraphQLでは、Deploymentオブジェクトのstatusesコネクションを利用します。GitHub公式のGraphQLスキーマでは、Deploymentに現在のstate、最新のlatestStatus、履歴を表すstatusesがそれぞれ定義されています。(GitHub Docs)

GitHub Enterprise Serverについては、今回のGitHub.com向け告知だけを根拠に同じ適用時期だと判断すべきではありません。GHESを利用している場合は、対象バージョンのリリースノートとAPIドキュメントを別途確認してください。

90日超のstatusが見えないときの切り分け方

deployment statusが取得できない原因は、保持期間だけとは限りません。権限不足やページネーションの見落としでも、似た状態になります。

症状考えられる原因確認方法
最近のstatusは取得できるが古いものだけない90日保持ポリシーstatusの作成時期を確認する
常に30件までしか取得できないREST APIのデフォルト件数per_page=100とページネーションを設定
APIが404を返すリポジトリ、deployment ID、権限の問題URLとトークン権限を確認
GraphQLで一部しか返らないカーソルページネーション不足pageInfo.hasNextPageを確認
deploymentのstateは見えるが履歴がない90日保持ポリシーの可能性が高いstatusesと現在のstateを分けて確認
Projectsの検索条件を変えても出ない検索フィルターの問題ではないRESTまたはGraphQLで元データを確認

REST APIの一覧取得はデフォルト30件、最大100件です。100件を超える場合は、レスポンスのLinkヘッダーに従って次のページを取得する必要があります。また、非公開リポジトリのdeploymentを取得するトークンには、原則としてリポジトリの「Deployments: read」権限が必要です。(GitHub Docs)

したがって、30件しか表示されない場合は保持期間ではなくページネーション、すべて取得できない場合は権限、古いstatusだけ選択的に消えている場合は90日保持ポリシーを疑うと切り分けやすくなります。

影響を受ける運用と受けにくい運用

影響を受けにくいケース

次のような用途では、現在のdeployment stateが維持されるため、影響は比較的小さくなります。

  • 現在、本番環境へのデプロイが成功状態かだけを確認する
  • 最新デプロイへのリンクだけをProjectsで参照する
  • 直近数週間の開発状況だけを確認する
  • 履歴を別のCI/CDツールですでに保存している

影響を受けるケース

一方、次の用途では外部保存が必要です。

  • 半年または1年単位でデプロイ成功率を集計する
  • 障害発生時に過去の失敗から成功までの遷移を調査する
  • 誰が、いつ、どの環境へデプロイしたかを監査する
  • リリースごとの説明、environment URL、log URLを残す
  • Projectsを使って長期のリリース履歴を可視化する
  • デプロイ頻度や変更失敗率を継続的に分析する
  • 顧客や監査部門にデプロイ証跡を提出する

deployment statusには、状態だけでなく、作成日時、更新日時、環境名、説明、実行者、environment URL、log URLなどが含まれます。現在のstateだけを残しても、これらの情報を後から再構成することはできません。(GitHub Docs)

推奨する保存構成は「Webhook+定期エクスポート」

長期保存では、次の二段構成が安定します。

GitHub deployment status
        │
        ├─ deployment_status Webhook
        │       └─ 発生直後のJSONを外部ストレージへ保存
        │
        └─ 日次のREST API照合
                └─ 取りこぼしを検出して追加保存

Webhookで新しいstatusをすぐ保存する

GitHubのdeployment_status Webhookは、新しいdeployment statusが作成されたときに送信されます。リポジトリ、Organization、GitHub Appで購読でき、payloadにはdeploymentとdeployment statusの情報が含まれます。(GitHub Docs)

Webhook方式には、次の利点があります。

  • status作成直後に保存できる
  • 90日の期限を意識せず蓄積できる
  • 全deploymentをAPIで繰り返し走査する必要がない
  • Organization配下の複数リポジトリを集約しやすい

ただし、Webhookだけに依存してはいけません。GitHub公式ドキュメントでは、stateがinactiveのdeployment statusについてはWebhookイベントが発生しないとされています。受信障害や設定ミスによる取りこぼしも考慮し、APIによる定期照合を併用するのが安全です。(GitHub Docs)

REST APIで日次または週次の照合を行う

REST APIでは、最初にdeployment一覧を取得し、各deployment IDに対してstatus一覧を取得します。

GET /repos/{owner}/{repo}/deployments
GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses

Fine-grained personal access tokenやGitHub Appトークンを使用する場合は、対象リポジトリの「Deployments: read」権限を付与します。(GitHub Docs)

90日間保持されるからといって、89日目にまとめて実行する設計は避けるべきです。バッチ停止、認証切れ、ネットワーク障害、休日対応の遅れが重なると、そのまま削除期限を超えるためです。

実務上の目安は次のとおりです。

規模・重要度実行間隔
監査対象の本番環境日次
一般的な業務システム日次または週次
検証用リポジトリ週次
Webhookを併用している大規模OrganizationWebhook即時保存+日次照合

告知では90日の境界がどの時刻基準で処理されるか、削除バッチがいつ実行されるかまでは示されていません。境界日に依存せず、十分な余裕を持って取得してください。(The GitHub Blog)

REST APIでdeployment statusをJSONLへ保存する例

次のスクリプトは、指定したリポジトリのdeploymentとstatusをJSON Lines形式で保存する例です。初回の退避や、小規模から中規模のリポジトリで利用できます。

#!/usr/bin/env bash
set -euo pipefail

OWNER="${OWNER:?OWNERを設定してください}"
REPO="${REPO:?REPOを設定してください}"
OUT_DIR="${OUT_DIR:-deployment-archive}"

FILE_STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
CAPTURED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

mkdir -p "${OUT_DIR}"

DEPLOYMENTS_FILE="${OUT_DIR}/${OWNER}-${REPO}-deployments-${FILE_STAMP}.jsonl"
STATUSES_FILE="${OUT_DIR}/${OWNER}-${REPO}-statuses-${FILE_STAMP}.jsonl"

: > "${STATUSES_FILE}"

gh api --paginate \
  -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2026-03-10" \
  "/repos/${OWNER}/${REPO}/deployments?per_page=100" \
  | jq -c '.[]' > "${DEPLOYMENTS_FILE}"

jq -r '.id' "${DEPLOYMENTS_FILE}" | while read -r deployment_id; do
  gh api --paginate \
    -H "Accept: application/vnd.github+json" \
    -H "X-GitHub-Api-Version: 2026-03-10" \
    "/repos/${OWNER}/${REPO}/deployments/${deployment_id}/statuses?per_page=100" \
    | jq -c \
      --arg repository "${OWNER}/${REPO}" \
      --arg deployment_id "${deployment_id}" \
      --arg captured_at "${CAPTURED_AT}" \
      '.[] | . + {
        repository_full_name: $repository,
        deployment_id: $deployment_id,
        captured_at: $captured_at
      }' >> "${STATUSES_FILE}"
done

echo "Deployments: ${DEPLOYMENTS_FILE}"
echo "Statuses:    ${STATUSES_FILE}"

実行時は、GH_TOKEN、OWNER、REPOを設定します。

export GH_TOKEN="github_pat_xxxxxxxxx"
export OWNER="example-org"
export REPO="example-repository"

bash export-deployment-statuses.sh

公式RESTドキュメントの現行サンプルではX-GitHub-Api-Version: 2026-03-10が使用されています。APIバージョンは将来更新される可能性があるため、運用開始時には最新の公式ドキュメントも確認してください。(GitHub Docs)

このスクリプトが出力したファイルは、ローカルディスクやActionsランナー上に残すだけでは不十分です。処理終了後、必ずS3互換ストレージ、Azure Blob Storage、Google Cloud Storage、社内オブジェクトストレージなどへ転送します。

GitHub Actionsで日次実行する場合の設定例

スクリプトを./scripts/export-deployment-statuses.shに保存した場合、次のようなワークフローで日次実行できます。

name: Archive deployment statuses

on:
  schedule:
    - cron: "17 2 * * *"
  workflow_dispatch:

permissions:
  contents: read
  deployments: read

jobs:
  export:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Export deployment statuses
        env:
          GH_TOKEN: ${{ github.token }}
          OWNER: example-org
          REPO: example-repository
        run: bash ./scripts/export-deployment-statuses.sh

      - name: Upload to external storage
        run: |
          tar -czf deployment-archive.tgz deployment-archive/

          # この部分を組織で利用している
          # S3、Azure Blob、GCSなどのアップロード処理に置き換える
          ./scripts/upload-archive.sh deployment-archive.tgz

GitHub Actionsのartifactは、一時的な受け渡しや処理確認には利用できますが、長期監査データの唯一の保存先にはしない方が安全です。最終保存先には、自社で保持期間、削除防止、暗号化、アクセス制御を管理できる外部ストレージを使用します。

GraphQLで保存する場合のクエリ例

GraphQLを利用しているシステムでは、Deployment.statusesをカーソルページネーションしながら保存します。

query DeploymentStatusArchive($deploymentId: ID!, $cursor: String) {
  node(id: $deploymentId) {
    ... on Deployment {
      id
      databaseId
      commitOid
      environment
      state

      statuses(first: 100, after: $cursor) {
        nodes {
          id
          createdAt
          updatedAt
          state
          description
          environment
          environmentUrl
          logUrl
          creator {
            login
          }
        }

        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  }
}

pageInfo.hasNextPageがtrueの間は、endCursorを次のcursorとして再実行します。最初の100件だけ保存して終了すると、status数が多いdeploymentで履歴を取りこぼします。GraphQLのDeploymentには、現在のstateと履歴のstatusesが別フィールドとして用意されています。(GitHub Docs)

外部ストレージに最低限保存すべき項目

単にAPIレスポンスをCSVへ変換するだけでは、後の用途に必要な関連情報が失われることがあります。最低限、次の項目を残してください。

項目用途
repository_full_nameリポジトリの特定
deployment_iddeploymentとの関連付け
deployment_status_id重複排除と更新判定
shaまたはcommitOid対象コミットの特定
refブランチ、タグの特定
environmentproduction、stagingなどの識別
statesuccess、failure、in_progressなど
descriptionデプロイ結果の説明
creatorstatusを作成した主体の確認
created_atstatus作成時刻
updated_atstatus更新時刻
environment_urlデプロイ先URL
log_url実行ログへの参照
captured_at自社側で取得した時刻
生のJSON payload将来の項目追加や再解析への備え

保存時は、deployment_status_idを一意キーとしてupsertできる構造にすると、Webhookと定期バッチの両方から同じstatusを受信しても重複しません。

また、log_urlを保存するだけではログ本文を保存したことにはなりません。リンク先のCI/CDログやartifactにも別の保持期限が設定されている可能性があるため、監査でログ本文が必要なら、ログそのものも別途アーカイブします。

保存先の選び方

保存先向いている用途注意点
オブジェクトストレージ生JSONの長期保存、監査証跡検索や集計には追加処理が必要
リレーショナルデータベースリポジトリ別、環境別の検索スキーマ変更への対応が必要
データウェアハウス成功率、頻度、失敗傾向の分析取り込みコストと権限管理が必要
SIEM・ログ基盤障害調査、横断検索、アラート長期保存コストを確認する
Gitリポジトリ小規模なJSONの簡易保存容量増大、機密情報、削除履歴に注意
GitHub Actions artifact一時的な処理結果長期保存の最終保管庫には不向き

実務では、生JSONをオブジェクトストレージへ保存し、分析に必要な項目だけデータベースやデータウェアハウスへ取り込む構成が扱いやすくなります。

運用で失敗しやすいポイント

90日ごとのバッチにする

90日間保持されるからといって、90日間隔で実行すると余裕がありません。日次または週次で実行し、最終成功日時を監視してください。

Webhookだけで完全だと考える

inactive stateではdeployment_status Webhookが発生しません。APIによる定期照合を残す必要があります。(GitHub Docs)

REST APIの先頭30件だけ保存する

REST APIはデフォルト30件です。per_page=100を設定したうえで、全ページを取得してください。(GitHub Docs)

GraphQLの最初のページだけ保存する

statuses(first: 100)だけでは101件目以降を取得できません。pageInfoを確認し、カーソルを更新しながら繰り返します。(GitHub Docs)

current stateだけを保存する

現在の状態だけでは、途中で何回失敗したか、いつ復旧したか、どのログに関連付けられていたかを確認できません。各statusをイベント単位で保存してください。

log URLだけで十分だと考える

リンク先のログが先に削除される可能性があります。監査要件にログ本文が含まれるなら、CI/CD側のログ保持も同時に設計します。

保存成功を監視していない

ワークフローが停止したまま90日を超えると、後から穴を埋められません。少なくとも次の項目を監視します。

  • 最終エクスポート成功日時
  • 保存したstatus件数
  • APIエラー件数
  • Webhookの最終受信日時
  • 取得した最新statusのcreated_at
  • 前回まで存在したリポジトリが対象外になっていないか

すでに90日を超えたstatusは復元できるのか

GitHub側で削除され、REST APIとGraphQL APIの両方に現れなくなったstatusを、APIオプションや検索フィルターで再表示することはできません。今回の告知でも、削除済みstatusを復元するAPIや保持期間を延長する設定は案内されていません。(The GitHub Blog)

復元できる可能性があるのは、別の場所に情報が残っている場合です。

  • GitHub Actionsの過去のworkflow run
  • 外部CI/CDサービスの実行履歴
  • デプロイツールのログ
  • SIEMや監視基盤
  • OrganizationまたはリポジトリのWebhook受信ログ
  • 過去に取得したRESTまたはGraphQLレスポンス
  • リリース管理システムや変更管理票

ただし、これらのログからGitHubのDeploymentStatusオブジェクトを完全に再現できるとは限りません。status ID、作成者、説明、environment URLなどが必要な場合は、削除前のAPIレスポンスを保存しておく必要があります。

今すぐ実施すべき対応

最初に、監査や分析の対象となるリポジトリを一覧化し、現時点で取得できるdeployment statusをすぐにエクスポートします。

次に、日次または週次のREST API取得を設定し、Organization規模で運用する場合はdeployment_status Webhookによる即時保存を追加します。保存先では、生JSONを残しながらdeployment_status_idを一意キーとして重複排除してください。

最後に、エクスポート処理そのものを監視します。90日保持は「90日後に対応すればよい」という猶予ではなく、取得に失敗し続けた場合にデータが失われる最終期限です。Projectsや現在のdeployment stateを長期履歴の代わりにせず、必要な証跡はGitHubの保持期限内に自社管理のストレージへ移すことが重要です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次