Dependabotのuvエコシステムが pyproject.toml の下限バージョンを壊す問題と対策

Dependabotのuvエコシステムが pyproject.toml の下限バージョンを壊す問題と対策

Dependabotのuvエコシステムはversioning-strategy未対応で、常にincrease戦略が適用されます。これにより pyproject.toml の下限バージョンがPyPIに存在しないバージョンに書き換えられ、CIが壊れることがあります。uv lock --check をCIに追加して防ぐ方法を紹介します。
2026.07.18

はじめに

ある日、CIが突然こんなエラーで落ちました。

× No solution found when resolving dependencies:
╰─▶ Because only boto3<=1.43.48 is available and your project depends on
    boto3>=1.43.49, we can conclude that your project's requirements are
    unsatisfiable.

boto3>=1.43.49 — そんなバージョン、PyPIに存在しません。誰がこんな制約を書いたのか?

犯人は Dependabot でした。

この記事では、DependabotのuvエコシステムがPythonプロジェクトの依存関係を壊すメカニズムと、それを防ぐCI対策について調べたことをまとめます。

dependabot-uv-versioning-pitfall-flow

何が起きたのか

発生した問題

pyproject.tomlboto3 の下限バージョンが、PyPIに存在しないバージョンに設定されていました。

pyproject.toml
dependencies = [
    "boto3>=1.43.49",  # ← このバージョンは存在しない
]

原因の特定

git log でこの制約がどこで入ったかを追跡しました。

git log -p --all -S "boto3" -- backend/pyproject.toml

すると、Dependabotが作成したPR内で >=1.43.0>=1.43.49 に書き換えられていたことがわかりました。

Dependabotの increase 戦略 — なぜ下限を引き上げるのか

versioning-strategy とは

Dependabotには依存バージョンの更新方法を制御する versioning-strategy という設定があります。

戦略 動作
increase 下限バージョンを最新の解決バージョンに引き上げる
increase-if-necessary 現在の制約で新バージョンが許容される場合は変更しない
lockfile-only ロックファイルのみ更新し、マニフェストは触らない
widen 新バージョンを含むように範囲を広げる(ライブラリ向け)

uvエコシステムの制限

ここが問題の核心です。Dependabotの uv エコシステムは versioning-strategy の設定をサポートしていませんdependabot-core #12162)。常に increase 戦略が使われ、下限バージョンが最新の解決バージョンに書き換えられます。

つまり、boto3>=1.35 と書いても、Dependabotが次のPRで boto3>=1.43.49 に変えてしまうのです。

>= vs ~= — どちらを使うべきか?

この問題に対して、~=(compatible release)を使えば解決するのではないかと考えました。

# >= : 下限のみ指定(上限なし)
"boto3>=1.35"

# ~= : compatible release(互換リリース)
"boto3~=1.43.0"  # >= 1.43.0, < 1.44.0

アプリケーションには >= が推奨

調査の結果、アプリケーション(ライブラリではないもの)には >= が推奨されることがわかりました。

アプリケーション ライブラリ
目的 自分のプロジェクトだけで使う 他のプロジェクトにインストールされる
推奨指定子 >=(緩い下限) >= または ~=
バージョン固定 uv.lock でピン留め 利用側の lockfile に委ねる
Dependabotの影響 lockfile の更新が主目的 マニフェストの更新も意味がある

アプリケーションでは pyproject.toml の制約は「このバージョンより古いものは動かない」という下限を示すだけで、実際にインストールされるバージョンは uv.lock が決めます。~= で上限を設けると、正当なマイナーバージョンの更新まで阻害してしまいます。

そして重要なこと: ~= にしても Dependabot は下限を書き換えるため、根本的な解決にはなりません。

pip エコシステムへの切り替えは解決にならない

Dependabotの pip エコシステムなら versioning-strategy: increase-if-necessary が使えます。これなら、>=1.35 の制約に対して 1.43.48 が出ても下限は変更されません。

dependabot.yml
- package-ecosystem: "pip"
  versioning-strategy: increase-if-necessary

しかし、大きな落とし穴があります。

pip エコシステムは uv.lock を更新しません。pyproject.toml は更新されるが uv.lock が古いままという、逆の不整合が発生します。

エコシステム versioning-strategy uv.lock 更新
uv サポートなし(常に increase する
pip increase-if-necessary 等をサポート しない

package-ecosystem は Dependabot のマニフェスト解析方法を指定するだけで、ローカルで使うパッケージマネージャには影響しません。しかし、lockfile を更新してくれないのであれば、使う意味がありません。

実践的な対策: CI ガード

下限の引き上げ自体を防ぐことはできない以上、壊れた制約がマージされるのを防ぐのが現実的な対策です。

uv lock --check を CI に追加

ci.yml
- name: Install Python dependencies
  run: cd backend && uv sync --group dev

- name: Verify dependency constraints are satisfiable
  run: cd backend && uv lock --check

- name: Lint (ESLint + ruff)
  run: pnpm run lint

uv lock --check は、pyproject.toml の制約と uv.lock の内容が整合しているかを検証します。制約が unsatisfiable(充足不能)な場合、このステップで明確なエラーメッセージとともに失敗します。

uv sync も同様のケースで失敗しますが、uv lock --check の方が:

  • エラーメッセージが明確: 依存関係の解決に特化したエラーが出る
  • 実行が速い: パッケージのインストールをスキップする
  • 意図が明確: CIのステップ名を見ただけで何をチェックしているかわかる

auto-merge との組み合わせに注意

Dependabotの自動マージを設定している場合、CI チェックが必須ステータスチェックに設定されているか確認してください。gh pr merge --auto は必須チェックが通るまで待機しますが、必須チェックが設定されていなければ即座にマージされます。

まとめ

学び 詳細
Dependabot uv エコシステムは versioning-strategy 未対応 常に increase が使われ、下限が引き上げられる
pip エコシステムへの切替は非推奨 uv.lock を更新しないため
~= や下限の緩和は一時的 次のDependabot PRで元に戻る
uv lock --check を CI に入れるのが現実解 unsatisfiable な制約のマージを防ぐ
auto-merge には必須ステータスチェックを設定する CIを通さずにマージされるのを防ぐ

Dependabotの uv エコシステムで versioning-strategy がサポートされるまで(#12162)、CIガードが最も実用的な防衛策です。Dependabotは便利なツールですが、その挙動を正しく理解しないと、かえってビルドを壊す原因になり得ます。

この記事をシェアする

関連記事