Dependabotのuvエコシステムが pyproject.toml の下限バージョンを壊す問題と対策
はじめに
ある日、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対策について調べたことをまとめます。

何が起きたのか
発生した問題
pyproject.toml の boto3 の下限バージョンが、PyPIに存在しないバージョンに設定されていました。
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 が出ても下限は変更されません。
- 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 に追加
- 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は便利なツールですが、その挙動を正しく理解しないと、かえってビルドを壊す原因になり得ます。






