---
title: "Renovateの緊急PRだけがCIで落ちる"
description: "pnpm 11 は公開から 24 時間未満のバージョンを既定で拒否し、Renovate は脆弱性修正の PR を即座に作るので、緊急の PR だけが CI で落ちます。Renovate 側でも待たせる案を考えていましたが、先行事例を調べると方向が逆でした。pnpm と Renovate の一次情報から、これからどう変えていくかを書きます。"
category: "DevOps"
tags: ["pnpm","Renovate","Security","GitHub Actions"]
publishedAt: "2026-09-17"
lastmod: "2026-09-17"
---

pnpm 11 は、公開から 24 時間未満のバージョンを既定で解決しません。Renovate は脆弱性修正の PR を検出と同時に作るようになっています。

この 2 つが組み合わさると、緊急の PR だけが CI で落ちます。当初は「Renovate 側でも 1 日待たせる」つもりでしたが、先行事例と一次情報を調べた結果、変えるのは `pnpm-workspace.yaml` の側になりそうです。

```yaml
# pnpm-workspace.yaml
minimumReleaseAge: 1440
```

## 緊急 PR だけ CI が常に落ちていた

hono の ReDoS 修正(GHSA-8j4g-w8fx-2239)に対して Renovate が `[security]` 付きの PR を作りました。変更は `pnpm-lock.yaml` の 1 ファイルだけです。その PR の CI が、次のエラーで落ちました。

```text
[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] 1 lockfile entries failed verification:
  hono@4.12.34 was published at 2026-08-03T02:30:36.000Z, within the minimumReleaseAge cutoff
```

修正版の公開が 8 月 3 日 02:30 UTC で、PR の CI が走ったのが 23:13 UTC です。24 時間に 3 時間ほど足りませんでした。翌日 04:18 UTC に CI を再実行すると通り、そのまま自動マージされています。

![hono@4.12.34 は 8 月 3 日 02:30 UTC に公開され、pnpm は翌 8 月 4 日 02:30 UTC までこのバージョンを拒否する。PR の CI は 8 月 3 日 23:13 UTC に走り、拒否される期間の中だったので落ちた。8 月 4 日 04:18 UTC の再実行は期間の外なので通った](/images/posts/pnpm-release-age-renovate-security-pr/release-age-timeline.svg)

コードには何の問題もなく、待てば通ります。ただ「緊急の PR は毎回落ち、人が時刻を見て再実行する」という運用は、それ自体が穴です。落ちている PR を見慣れると、本当に壊れている PR を見分けられなくなります。

## なぜ落ちるのか

両側の既定値を並べると、落ちるのは仕様です。

[pnpm の `minimumReleaseAge`](https://pnpm.io/settings/dependency-resolution) は v11 から既定が 1440 分です。「In most cases, malicious releases are discovered and removed from the registry within an hour.」という理由で、新しいバージョンの解決を遅らせます。[v11.1.3](https://github.com/pnpm/pnpm/releases/tag/v11.1.3) からは、ロックファイルを読み込んだ直後、tarball を取りに行く前に全エントリを再検証するようになりました。`--frozen-lockfile` も `pnpm fetch` も対象です([PR #11583](https://github.com/pnpm/pnpm/pull/11583))。ロックファイルの解決は Renovate 側で済んでいても、CI の `pnpm install` が改めて公開時刻を見て弾きます。

[Renovate の `vulnerabilityAlerts`](https://docs.renovatebot.com/configuration-options/#vulnerabilityalerts) の既定は、ソースの `lib/config/options/index.ts` を読むと `minimumReleaseAge: null`、`prCreation: 'immediate'` です。通常の更新には `config:best-practices` 経由で npm に 3 日の待機(`security:minimumReleaseAgeNpm`)が入りますが、脆弱性修正だけはその対象外で、検出したら即座に PR になります。この「npm にだけ 3 日」は、GitHub Actions の Digest 更新を automerge から外したときに確かめた設定と同じものです。

::card[https://labee.jp/posts/renovate-github-actions-digest-automerge-off]

つまり、通常の更新は Renovate が 3 日待ってから PR を作るので pnpm の 24 時間に引っかからず、緊急の PR だけが「Renovate は待たない、pnpm は待つ」という組み合わせになります。

リポジトリの `packageManager` に pnpm 11 を書いていて、CI で `pnpm install` が走るなら、どのリポジトリでも同じように落ちます。

## 他ではどうしているかを調べた

最初に考えたのは、共有の Renovate 設定に `vulnerabilityAlerts: { minimumReleaseAge: "1 day" }` を足すことでした。pnpm が 24 時間止める以上、前倒しで PR を作っても実利がないから失うものはない、という理屈です。

手を付ける前に、同じ問題を扱った記事と Issue を集めました。

- [pnpm のブログ](https://pnpm.io/blog/2025/12/05/newsroom-npm-supply-chain-security)に載った Seattle Times の運用は、Shai-Hulud(2025 年 11 月、796 パッケージ、検知から削除まで約 12 時間)を待機期間で防ぎつつ、緊急パッチは除外リストにバージョンを追加して通しています。
- [Christian Schneider 氏の記事](https://christian-schneider.net/blog/dependency-cooldowns-supply-chain-defense/)は「Cooldowns should apply to routine updates, not emergency security patches.」と書き、未知の更新は既定で遅らせ、既知のセキュリティ修正は意図して早く通す、と整理しています。
- [Andrew Nesbitt 氏の記事](https://nesbitt.io/2026/03/04/package-managers-need-to-cool-down.html)は、William Woodruff 氏が調べた 10 件の攻撃のうち 8 件で悪用できる期間が 1 週間未満だったという集計を引いたうえで、Bundler には CVE の修正をすぐ入れるための `--cooldown 0` という逃げ道がある、と挙げています。
- [Safeguard の記事](https://safeguard.sh/resources/blog/minimum-release-age-cooldown-policies-for-new-package-versions)は、chalk と debug の乗っ取り(2025 年 9 月)が約 2 時間で削除された一方、例外のない待機期間は公開済みの脆弱性に対する修正も同じ日数だけ止めてしまう、と指摘しています。

共通しているのは「待機は通常更新に、緊急修正には例外経路を」です。脆弱性修正を Renovate 側でも 1 日遅らせる案は、公開済みの脆弱性に対して自分から修正を遅らせる選択で、この線とは逆でした。

## Renovate は例外経路を持つ

例外経路は、Renovate に実装済みでした。[Renovate 42.89.0](https://github.com/renovatebot/renovate/releases/tag/42.89.0)(2026 年 1 月)から、脆弱性修正の PR では `pnpm-workspace.yaml` の `minimumReleaseAgeExclude` に `hono@4.12.34` の形で修正版を自動追記します([PR #40020](https://github.com/renovatebot/renovate/pull/40020))。8 月の PR にそれが起きなかった理由を、`lib/modules/manager/npm/artifacts.ts` で確かめました。

```typescript
if (!doc.get('minimumReleaseAge')) {
  return null;
}
```

執筆時点での分析では、追記が走るのは、`pnpm-workspace.yaml` に `minimumReleaseAge` キーが書かれているときだけでした。
pnpm 11 の組み込み既定に頼っていると、Renovate から見れば「待機期間は設定されていない」ので、何もしません。

![通常の更新は Renovate が 3 日待ってから PR を作るので pnpm の 24 時間の検証を通る。脆弱性修正は Renovate が即座に PR を作るので、修正版の公開から 24 時間以内に検出されると pnpm 側の検証に引っかかる。pnpm-workspace.yaml に minimumReleaseAge が明示されていれば、Renovate が minimumReleaseAgeExclude に修正版を追記するので検証を通る](/images/posts/pnpm-release-age-renovate-security-pr/security-pr-path.svg)

pnpm 側から見ても、明示する方が筋が通ります。[`minimumReleaseAgeStrict`](https://pnpm.io/settings/dependency-resolution) は `minimumReleaseAge` を明示したときだけ既定で true になり、組み込みの 1440 分は後方互換のために非 strict です。非 strict では、範囲内に待機期間を過ぎたバージョンがないとき pnpm が待機期間を無視して解決してしまいます。明示して strict にすれば、CI では `ERR_PNPM_NO_MATURE_MATCHING_VERSION` で止まり、例外は `minimumReleaseAgeExclude` にバージョン指定で書く、という 1 本の経路に揃います。

## 今後の対応方法

対処は次の 2 つにする予定です。

1. 各リポジトリの `pnpm-workspace.yaml` に `minimumReleaseAge: 1440` を明示します。値は既定と同じですが、Renovate がこのキーを読めるようになり、pnpm 側も `minimumReleaseAgeStrict` が既定で true に変わります。
2. Renovate の `vulnerabilityAlerts` には手を入れません。脆弱性修正は即座に PR を作らせ、Renovate が同じ PR で `minimumReleaseAgeExclude` に修正版を書く経路に任せます。

1 を入れたあとの脆弱性修正 PR では、`pnpm-workspace.yaml` の差分がこの形になるはずです。

```yaml
# pnpm-workspace.yaml
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
  - hono@4.12.34
```

`minimumReleaseAgeExclude` は `pnpm audit --fix` も同じ場所に書くので、Renovate を経由しない緊急修正も同じファイルの同じキーに集まります。

とはいえ、この対応が正しいか？と言われると甚だ怪しいかと思います。

## Renovate 側に残っている Issue

自動追記が必要以上に広く動く不具合が [Issue #42190](https://github.com/renovatebot/renovate/issues/42190) として残っています。修正版が既に十分古くても `minimumReleaseAgeExclude` に追記されるので、除外リストに古いエントリが溜まります。

溜まったエントリを消す対応は別途起票されていますが、まだ入っていないようです。

除外リストは pnpm 側で `pkg@version` の完全一致なので、古いエントリが残っていても新しい脆弱なバージョンを通すことはありませんが、溜まった分は定期的に手作業で消すことになります。

Renovate が pnpm の `minimumReleaseAge` を読んで自分の待機期間に反映する機能は、[Issue #41661](https://github.com/renovatebot/renovate/issues/41661) として open のままなので、今は両側に同じ値を書くしかなさそうです。