Skip to content

fix(ci): Dependency Unblock Check の probe 後の GitHub API 呼び出しを再試行し fetch failed の cause を出す #322

Description

@kmryst

目的

Dependency Unblock Check の full モードが、probe 実行後の GitHub API 呼び出しで fetch failed になり、
MECHANISM: Unexpected evaluator error: fetch failed(exit 1)で失敗する不具合を直す。
あわせて、同種の失敗が起きたときに原因(error.cause)をログから特定できるようにする。

失敗した run

run ID 日時 (UTC) Node.js 結果
34812644584 2026-09-14 06:14 24.20.0 success
36388464626 2026-09-28 06:50 24.21.0 fetch failed
37275307924 2026-10-05 07:00 24.21.0 fetch failed
  • いずれも schedule(main)、step Run dependency unblock check
  • 9/14 と 9/28 は同一コミット(1ae885f)。差分はランナーイメージ(20260907.300 → 20260920.314)に伴う Node.js 24.20.0 → 24.21.0 のみ
  • 2026-09-21(run 35567855585)の失敗は UNBLOCKED: jsdom@30.1.0(exit 10)で、本不具合とは別(設計どおりの赤)

根本原因

  • runFull は runProbes で spawnSync を使うため、probe 中(約 90 秒)イベントループが止まる
  • probe 前の getIssue / listLabeledOpenIssues で使った HTTP keep-alive の接続が、その間にサーバー側で閉じられる
  • Node.js 24.21.0 同梱の undici 7.29.1 は idle socket の検証を ref'd setImmediate に遅延する変更
    (nodejs/undici#5769、#5707 の backport)を含み、
    イベントループ再開直後の同期的な再利用では切断を検知できず、閉じた socket に書き込む
  • 失敗したのは probe 後の postUnblockComments → listIssueCommentBodies(GET /repos/{owner}/{repo}/issues/{n}/comments)
  • main() の catch が想定外の例外を Unexpected evaluator error: ${error.message} に丸め、error.cause を捨てているため、ログには fetch failed しか出ていなかった

ローカル再現

fetch(GET)→ spawnSync("sleep", [N]) → fetch(GET)の順で実行した結果。

Node.js 待ち時間 2 回目の fetch
24.21.0 90 秒 EPIPE
24.21.0 30 秒 UND_ERR_SOCKET
24.21.0 10 秒 成功
24.20.0 / 24.18.0 90 秒 成功
  • 24.21.0 でも、2 回目の前に setImmediate / setTimeout(0) を 1 回待つ、または非同期 spawn にすると成功する
  • 1 回目の失敗後に同じ GET を再試行すると、新しい接続で成功する

影響範囲

対象

  • scripts/ci/dependabot-unblock-check.mjs
    • createGitHubClient の request: GET に限り、TypeError: fetch failed かつ error.cause.code が
      UND_ERR_SOCKET / EPIPE / ECONNRESET のとき 1 回だけ再試行する
    • main() の catch: Unexpected evaluator error のメッセージに error.cause の code / message を含める
  • scripts/ci/dependabot-unblock-check.test.mjs: 回帰テストを追加する
  • docs/operations/dependency-unblock-check.md: 「赤(MECHANISM)の理由と対処」表への行追加と、再試行の仕様の追記

受け入れ条件

  • GET が上記 cause の fetch failed で 1 回失敗しても、再試行で成功すれば full モードが正常に判定を返す
  • 再試行は 1 回だけで、2 回目も失敗した場合や対象外の cause・HTTP エラー(!response.ok)は再試行せず MECHANISM になる
  • POST(createIssueComment)は再試行しない(コメントの重複投稿を避ける)
  • MECHANISM の Summary / ::error:: に cause の code と message が出る(例: fetch failed (cause: EPIPE write EPIPE))。トークン値は含まない
  • dependabot-unblock-check.test.mjs に、注入した fetchImpl で次を確かめるテストがある
    • 対象 cause で 1 回失敗 → 再試行で成功する
    • 2 回連続で失敗 → 例外になる(呼び出しは 2 回)
    • 対象外の cause と POST は再試行しない
    • 例外メッセージに cause が含まれる
  • node --test scripts/ci/dependabot-unblock-check.test.mjs と npm run lint:md が通る
  • Node.js 24.21.0 で「GET → spawnSync で 90 秒待機 → 修正後クライアントで GET」の再現スクリプトが成功する(PR に結果を記録)
  • docs/operations/dependency-unblock-check.md の「赤(MECHANISM)の理由と対処」表に Unexpected evaluator error: ... の行(意味と対処)があり、再試行の仕様が書かれている
  • マージ後、ADR-0008 の「リリース手順(タグ運用)」に従い v1.7.2(patch)を作成し、v1 タグを付け替える(force push を伴うため、実行前にユーザー確認を取る)

補足

  • 修正方針は「GET の 1 回再試行」を採る。次の 2 案は採らない
    • probe 後に setImmediate を 1 回待つ案: 再現では有効だが undici 内部の検証タイミングに依存し、将来の undici で再び壊れうる。注入した fetchImpl での単体テストもできない
    • probe を非同期 spawn にする案: 根本的だが createSpawnRunner / runProbes とテストの変更が大きく、今回の不具合に対して過剰
    • 再試行案は undici の実装に依存せず、keep-alive 接続の切断全般(NAT の idle timeout 等)にも効く。既存の fetchImpl 注入点でテストできる
  • 再試行を GET に限るのは、GET が冪等なため(RFC 9110 の idempotent method)。今回失敗したのも probe 後最初の GET で、再試行で新しい接続に切り替われば直後の POST は成功する
  • ticket-c2c-platform は @v1.7.1 に固定しているため、v1.7.2 を参照させる caller の更新は ticket-c2c-platform 側の Issue / PR(または Dependabot の github-actions 更新 PR)で別途行う
  • 「定期チェックの連続失敗に気づけない」(GitHub Actions の失敗通知メール以外に仕組みがない)問題は本 Issue の対象外とし、別途検討する

Activity

  1. added
    area:ci-cdCI/CD関連(GitHub Actions)
    area:docsドキュメント関連
    risk:low低リスク・影響範囲小・ロールバック容易
    cost:noneコスト影響なし
    on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:ci-cdCI/CD関連(GitHub Actions)area:docsドキュメント関連cost:noneコスト影響なしrisk:low低リスク・影響範囲小・ロールバック容易type:bugバグ修正

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions