Simon Willison、Ruff v0.16.0でCI障害に遭遇。デフォルト設定が変わっていた
- Martin Chen

- 1 日前
- 読了時間: 20分
Simon Willisonは、Ruff v0.16.0によってデフォルトのlintルールが59件から413件へ拡張された後、複数のCIジョブが失敗していることに気付いた。バージョンを固定していなかったRuffの開発依存関係が、新リリースを既存のPythonプロジェクトへ知らないうちに取り込んでいた。
Astralは2026年7月23日にこのバージョンをリリースした。2日後、Willisonは意図的なアップグレードを行わないまま、この更新が自身のビルドに到達した経緯を説明した。この障害は、リンターのリリースを依存関係管理に関する実践的な警告へと変えた。
核心にある対立は、より厳格なlintとより弱いコードの間のものではない。Ruffの設定不要での安全性向上と、変更されていないリポジトリに対して開発者が期待する安定性との間の対立だ。この緊張関係は、CIが実行のたびに利用可能な最新の開発ツールをインストールするあらゆる場面で重要になる。
Ruff v0.16.0が変えた「デフォルト」の意味
Ruff v0.16.0における最も重大な変更は、新しいコマンドではない。設定されていないプロジェクトがエラーと見なすべきものの定義が、大幅に広がったことだ。
RuffはRustで書かれたPythonのリンターおよびフォーマッターである。リンターは、プログラムを実行せずにソースコードを分析し、誤り、疑わしいパターン、特定のスタイル上の問題を検出する。
今回のリリース以前は、プロジェクトが明示的なlint選択を指定しない場合、Ruffは59個のルールを有効にしていた。Astralのmigration guideによると、バージョン0.16.0では同じ条件下で413個のルールが有効になる。
これは、有効なチェックが354件増えたことを意味する。また、デフォルトのRuffインストールが以前のほぼ7倍のルールを評価するようになったことも意味する。
周辺のルールカタログも成長している。Ruffのデフォルトが最後に変更されたバージョン0.1.0時点で、Ruffは708個のルールをサポートしていた。Astralによれば、現在のコレクションには968個のルールが含まれる。
以前のデフォルトは、主にPyflakesとpycodestyleの一部を選択していた。プロジェクトは、多くの統合ルール群について詳細な選択をすぐに行わずともRuffを導入できた。
この保守的な基準は、Ruffが既存のリポジトリに適合する助けとなった。一方で、ツールが把握していることと自動的に報告することの間には、広がり続ける隔たりも生じていた。
Astralはバージョン0.16.0で、その隔たりの大部分を埋めた。新しいデフォルトには、flake8-bugbear、pyupgrade、Ruff独自のRUFカテゴリを含む追加のルール群が取り込まれている。
Flake8-bugbearは、起こり得るバグや問題のある設計パターンに焦点を当てる。Pyupgradeは、プロジェクトがサポートするPythonバージョン向けに最新化できる構文や標準ライブラリのパターンを特定する。
default rule listには現在、構文上の問題や即座に実行時エラーを引き起こす問題を露出できるチェックが含まれている。これらは単に空白や命名に関する好みではない。
この違いが、RuffによるCI障害に注意を払う価値がある理由を説明する。新しい報告の一部は、以前はRuffが関連する検出器を自動で有効にしていなかったために通過していた不具合を明らかにする。
ほかの報告は、保守性、モダナイゼーション、またはチームが意図的に受け入れているパターンに関するものになる。より大きなデフォルトセットであっても、すべてのリポジトリの互換性要件や設計規約を知ることはできない。
したがって、Ruff v0.16.0は同時に2つのことを変える。自動的な不具合検出を増やすとともに、7月23日以降の最初のアップグレード時に、より多くのポリシー判断を持ち込む。
このリリースでは、Markdownファイル内のPythonコードブロックもデフォルトで整形されるようになった。サポートされるフェンス付きブロックには、python、py、python3、py3、pyi、pyconが含まれる。
この挙動は、ドキュメント、チュートリアル、またはQuartoノートブックを含むリポジトリに影響する。フォーマットチェックが、従来の.pyファイル以外の変更を識別できるようになるためだ。
Ruffのrelease notesでは、新しい抑制コメントと、より充実した診断出力についても説明されている。これらの改善は、追加の検出結果が現れた際に開発者が対応する助けとなる。
ただし、範囲の変更こそが主要な移行イベントである。先週まで予測どおりに動作していたコマンドが、まったく同じソースコードに対して今日になって非ゼロの終了コードを返す可能性がある。
Simon WillisonのCI障害が重要な理由
Simon Willisonの経験は、開発ツールの更新が、リポジトリ自体を変更せずにその実効的なポリシーを変え得ることを示している。
Willisonは、Python、データツール、生成AIに関わるプロジェクトで知られる独立系開発者・ライターである。キャリア初期にはDjango Webフレームワークの共同開発者でもあった。
7月25日、Willisonは自身の「さまざまなCIジョブ」が失敗し始めたと記した。原因を、新しいRuffのデフォルトと、バージョン固定されていない"ruff"開発依存関係に突き止めた。
彼のRuff accountは、このリリースを具体的なユーザーの視点から捉えている。リポジトリのコード自体が必ずしも後退したわけではないが、その下で検証環境が変化していた。
CIジョブ、すなわち継続的インテグレーションジョブは、開発者が変更を提案またはマージするたびに自動チェックを実行する。チームは、コードを受け入れて安全かを判断するために、一貫した結果へ依存している。
ジョブがバージョン制約なしでruffをインストールすると、パッケージリゾルバーは利用可能な最新リリースを選択できる。すると次のビルドでは、メンテナーが明示的にレビューしていない挙動が強制される可能性がある。
Ruffは通常、開発依存関係であるため、この失敗モードは軽視されがちだ。通常は、ユーザーに提供されるアプリケーションには同梱されない。
しかし開発依存関係は、ソフトウェアがデリバリーパイプラインを通過できるかを左右する。リンターの新しい終了コードは、プルリクエストをブロックし、リリースを停止させ、数時間の調査を要することがある。
この事例は、ランタイム依存関係とツールの間にある誤解を招きやすい区別も露呈させる。ランタイムパッケージはデプロイ済みソフトウェアの動作に影響し、ツールは開発者がそれをデプロイできるかどうかに影響する。
どちらも運用上の変更をもたらし得る。ただし、システム内の異なる地点で作用するだけだ。
Ruffの拡張されたルールは、リリースどおりに機能していたため、Willisonの経験は特に有益である。この障害に、破損したパッケージ、侵害されたレジストリ、不具合のあるインストーラーは必要なかった。
ツールは正常にインストールされた。新しいポリシーのもとでプロジェクトを正しく検査した。CIが失敗したのは、そのポリシーがリポジトリが暗黙に依存していたものと異なっていたためだ。
これは再現性の問題である。再現可能なビルドまたはチェックは、同じソースと宣言済み入力から同等の結果を生み出すべきだ。
「最新のRuff」は安定した入力ではない。パッケージマネージャーがいつ解決するかによって意味が変わる、移り変わる要求である。
ロックファイルと厳密な制約は、この入力を明示的にできる。更新管理サービスはその後、制御されたアップグレードを提案できるため、メンテナーはバージョン変更をマージする前に新しい診断結果を確認できる。
この教訓はRuffにとどまらない。フォーマッター、型チェッカー、テストランナー、ドキュメントビルダー、セキュリティスキャナーはいずれも、リリース間でデフォルトを改定し得る。
アプリケーションライブラリを固定していても、開発ツールが変動するリポジトリは、部分的にしか再現可能ではない。本番環境での挙動は固定されたままでも、本番へ至る経路は変化する可能性がある。
この懸念は自動コーディングシステムにとり特に重要だ。エージェントはしばしばリポジトリのチェックを実行し、その出力を解釈し、すべてのゲートを通過するまでコードを修正する。
それらのゲートの背後にあるツールが予期せず変化すれば、エージェントは動く標的に直面する。検出結果が現れた理由を理解しないまま、不必要な編集を生成したり、検出結果を抑制したりするかもしれない。
検索可能なengineering knowledge baseを構築するチームは、設定やCI履歴とともにアップグレード判断を保存できる。この文脈は、将来のメンテナーが意図的なポリシーと偶発的なドリフトを区別する助けとなる。
Simon Willisonが明らかにしたRuffの新しいトレードオフ
Ruffのより広範なデフォルトは初回実行時のカバレッジを改善するが、設定を省略することを安定した契約と見なしていたプロジェクトに移行作業を移す。
Astralの立場は明快だ。Ruffには数百のチェックが蓄積された一方、デフォルト選択は固定されたままで、設定を行わないユーザーにとって重大な診断が有効化されない状態となっていた。
以前の選択はRuff v0.1.0までさかのぼる。その後、ルールカタログは708件から968件へ、260件増加した。
59件のチェックだけを有効に保つことは、Ruffの設定不要の体験が、その機能の縮小し続ける一部しか表さないことを意味した。新しいユーザーは、デフォルトが実際より包括的だと考える可能性があった。
このリリースは、その不一致に対処する。開発者は、数百のルールコードをまず学ばなくても、構文エラー、実行時の危険、モダナイゼーションの機会、疑わしい構文を発見できるようになった。
これは小規模プロジェクトにとって価値がある。また、メンテナーが詳細なlintポリシーを整備する前から、適切なカバレッジを求める新規リポジトリにも有益だ。
対照的な期待も同じく妥当である。特にドキュメントが設定なしで利用可能なツールとして提示している場合、デフォルトはしばしば製品の挙動として扱われる。
lint.selectを省略する開発者は、Ruffの保守されたベースラインを選んでいると考えるかもしれない。バージョン0.16.0以前は、そのベースラインがアップグレードをまたいで安定し続けることにも依存していた。
Astralがベースラインを変更したのは、手を付けずにおくことにもコストがあったためだ。プロジェクトは、インストール済みバイナリがすでに検出方法を知っているエラーを含んでいても、Ruffを通過し得た。
したがって、このトレードオフは安全性と利便性の間のものではない。より広範な自動保護と、アップグレード時の予測可能性の間のものだ。
より狭いデフォルトはアップグレード時の驚きを減らす一方、新しいユーザーからより多くの検出結果を隠す。より広いデフォルトはより多くの不具合を明らかにするが、確立されたパイプラインを混乱させる可能性がある。
Ruff v0.16.0は、次回実行時により強い保護を選択する。以前の契約を望むプロジェクトは、今後その選好を明示的に記録しなければならない。
Astralは直接的な互換性設定を提供している。
設定がスタンドアロンのruff.tomlに置かれている場合、正確な表は異なることがある。重要なのはファイル名ではなく、明示的なルール選択である。
この設定は以前のデフォルトルール群を復元する。チームに余裕を与えつつ、バージョン0.15を無期限に固定することを強いるものではない。
ただし、以前の挙動を復元することは、すべての新しいチェックを自動的に却下することではなく、移行の一段階であるべきだ。一部の障害は、ただちに修正すべきバグを特定する可能性がある。
慎重なアップグレードは、完全な診断出力を取得することから始まる。その後、メンテナーは検出結果をルールコード、重大度、修正の安全性、互換性への影響ごとに分類できる。
確実な構文または実行時の問題を露出するルールを優先すべきだ。機械的なモダナイゼーションに関する検出結果は、可能であれば焦点を絞ったコミットで、別途レビューできる。
ポリシー志向のチェックにはチームの判断が必要だ。あるパターンは、生成ファイル、フレームワークの慣習、互換性モジュール、または安易に変更できない公開APIにとって有効である場合がある。
Ruffは、そのようなケース向けにファイル単位のignoreと対象を絞った抑制をサポートしている。バージョン0.16.0では、既存のnoqaの挙動に加えて、ruff: ignoreおよびruff: file-ignoreコメントが追加された。
対象を絞った抑制は通常、広範な除外よりも監査しやすい。ルールが適合しない箇所を記録でき、将来のメンテナー向けに理由を含めることもできる。
それでも、既存の違反が一度に数百件現れると、抑制は煩雑になり得る。メンテナーが意図的なクリーンアップを予定するまでは、プロジェクト全体のルール選択のほうがより正直かもしれない。
正しい対応はリポジトリの成熟度に依存する。新しいプロジェクトはより広いベースラインをすぐに受け入れられる一方、大規模なレガシーコードベースでは段階的な導入が必要になる可能性がある。
これが、デフォルト変更が通常とは異なる重みを持つ理由だ。大きく異なる履歴と制約を持つプロジェクトに、単一の製品レベルの判断を適用するからである。
新しいデフォルトは移行の一部にすぎない
最初の診断結果の波に対応したチームでも、Markdown の書式設定、機械可読な出力、抑制動作を見直す必要がある。
Ruff v0.16.0 により、Markdown 内の Python コードブロックがフォーマッターの通常の対象に含まれるようになった。これにより、README、ドキュメントページ、ノートブック風の公開用ファイルが変更される可能性がある。
フォーマッターは、フェンス付きコードブロックに付与された一般的な Python 情報文字列を認識する。pyi はスタブコードとして、pycon は対話型 Python セッションとして扱う。
Quarto ユーザーは、{python} のような形式で指定されたブロックも整形できる。.qmd ファイルを使うプロジェクトでは、Ruff の対象に含める前に拡張子マッピングが必要になる場合がある。
この機能により、ドキュメント内の例とソースコード用フォーマッターの整合性が取れる。コピーされた例が古い、あるいは一貫性のない書式のまま使われる可能性を減らせる。
一方で、これまで ruff format --check が従来型のソースファイルだけを検査していた場合、予期しない CI 失敗を引き起こすこともある。ドキュメントの管理者が初めて Ruff のポリシーに直面する可能性がある。
必要に応じて、プロジェクトは extend-exclude で Markdown を除外できる。選択した領域の周囲に書式設定抑制コメントを使うことも可能だ。
この判断は、コードサンプルが実行可能なガイダンスなのか、慎重に配置された説明用の素材なのかを反映すべきだ。自動整形は後者より前者に対して、より一貫して有効である。
診断表示も変わった。Ruff は通常の check および format --check 出力内に、提案される差分を表示するようになった。
従来、開発者は差分を別途要求できた。新しい完全出力では診断と提案変更が一緒に表示されるため、失敗したチェックを解釈しやすくなる。
CI プロバイダー向けには、format --check が GitHub および GitLab の注釈に使われる出力形式をサポートするようになった。書式上の問題をコードレビュー内の該当行に直接表示できる。
機械的に出力を利用する側は、より注意が必要だ。Ruff の JSON 出力にある複数のフィールドは、プレースホルダーの位置情報ではなく null になる場合がある。
影響を受けるフィールドには、filename、location、end_location、および修正編集内の対応する位置情報が含まれる。すべての位置情報がオブジェクトまたは文字列であると仮定するコンシューマーは失敗する可能性がある。
これは大半のユーザーにとっては小さな破壊的変更だ。しかし、Ruff の出力をダッシュボード、レビューボット、カスタム品質システムに取り込むチームにとっては、より重要になる。
したがって、更新後のパイプラインは 3 つの層で失敗する可能性がある。Ruff が新しい違反を見つける、整形対象が新しいファイル形式へ広がる、あるいは出力パーサーが null 許容フィールドを拒否する、というものだ。
すべての失敗を「lint ルールが増えた」と扱うと、実際の原因を見逃すおそれがある。メンテナーはアプリケーションコードを編集する前に、どの層が変化したのかを特定すべきだ。
新しい抑制形式もポリシーの見直しに値する。行末の ruff: ignore[F401] は、その診断に対する対象限定の noqa のように動作する。
直前のコメントは、次の論理行に対する検出結果を抑制できる。報告された問題が関連するトークンの横にきれいに収まらない、複数行の関数ヘッダーなどで役立つ。
ファイル全体の抑制には ruff: file-ignore を使用できる。理由を含められるため、説明のない一律除外よりもレビュー担当者に多くの情報を与えられる。
新しい --add-ignore オプションは、抑制を自動で挿入できる。この利便性が、根本の診断が実際の欠陥を示すものかどうかのレビューに取って代わるべきではない。
自動化はコメントを追加して、すぐに CI をグリーンにできる。しかし、プロジェクトがその例外を何年も抱えるべきかどうかは判断できない。
Ruff は安全と見なす修正と、unsafe-fix オプションを必要とする修正を分けている。安全という分類であっても、生成コード、公開 API、通常とは異なる実行時動作の文脈でレビューすべきだ。
Python の動的な振る舞いは、静的解析で保証できる範囲を制限する。Ruff 自身の fix guidance でも、安全な修正がコードを損なう事例を報告するようユーザーに求めている。
この制約は lint の意義を弱めるものではない。検出、自動変更、人間による承認を区別する必要性を強調するものだ。
バージョン固定されていない開発ツールが今や焦点になる
直接的な圧力がかかるのは、ルール選択を暗黙のままにしつつ Ruff を動的にインストールしているリポジトリだ。
明示的な select リストを持つ完全に固定された Ruff バージョンには、安定した 2 つの制御がある。一方はツール実装を固定し、もう一方はプロジェクトが選択した lint ポリシーを固定する。
明示的なルールを持つフローティングバージョンには、部分的な安定性がある。新しい Ruff リリースによって、個々のルール動作、解析、出力、整形、設定セマンティクスが変わる可能性は依然としてある。
明示的なルールを持たない固定バージョンにも、部分的な安定性がある。CI はメンテナーが Ruff を更新するまで一貫性を保つが、更新時にはデフォルト移行が一度に到来する。
明示的なルールを持たない固定されていないバージョンには、どちらの制御もない。この組み合わせが、Simon Willison の Ruff CI 失敗の背景となる条件を生んだ。
固定はツールを無期限に凍結することを意味しない。アップグレードを発見することと、採用することを分けるものだ。
依存関係更新のプルリクエストは、明確なレビュー境界を作る。既存の main ブランチの再現性を保ったまま、CI で新たな検出結果を示せる。
その後、メンテナーはいくつかの対応を選べる。
新しいデフォルトで特定された明確な欠陥を修正する。
安全な機械的変更を分離したコミットで受け入れる。
リポジトリ固有のパターンに対し、意図的な例外を設定する。
以前の選択に戻し、段階的なルール導入を予定する。
null 許容の JSON 位置情報を扱えないパーサーを更新する。
手動書式を保つ必要があるドキュメントファイルを除外する。
これらの作業を無差別に混在させるべきではない。単一の大規模な autofix コミットは、何千もの書式編集の中に動作変更を隠してしまう可能性がある。
ルールファミリーごとに作業をまとめると、レビューが明確になる。また、あるルールがプロジェクトでサポートする Python バージョンと衝突した場合も、ロールバックしやすい。
pyupgrade ルールが有効な場合、対象バージョンの設定は重要だ。モダンな構文は、あるインタープリタの基準では正しくても、別の基準では使用できない可能性がある。
チームは Ruff に設定した Python ターゲットが、実際のデプロイ環境と一致することを確認すべきだ。そうでなければ、モダナイゼーションの提案が本番環境のサポートを先走る可能性がある。
生成コードにも別の扱いが必要だ。生成ファイルの再整形や lint は、次回ジェネレーターを実行したときに消える変更を作りがちだ。
生成パスを除外する方が、抑制コメントで埋め尽くすより正確な場合がある。品質を担保する適切な場所は通常、ジェネレーターのソースまたはテンプレートだ。
モノレポには別の複雑さもある。異なるパッケージが異なる Python バージョンをサポートしたり、個別の lint ポリシーを維持したりする場合がある。
ルートレベルのデフォルトは運用を簡素化できるが、無関係なコンポーネントに単一の移行スケジュールを課すことにもなる。パッケージごとの設定の方が、所有権を適切に反映できるかもしれない。
主な懐疑的な問いは、413 のルールが広く受け入れられるベースラインを構成するかどうかだ。Astral はこの選択を文書化しているが、実際の導入でその誤検知率と互換性の負担が検証される。
Willison の失敗は初期のシグナルの一つであり、代表的な調査ではない。混乱が起こり得ることを示すものであって、大半の Ruff ユーザーがそれを経験することを示すものではない。
すでに明示的な select または extend-select を使用しているプロジェクトは、異なる対応を取れる。有効なルールセットは、その設定が新しいベースラインとどう相互作用するかに左右される。
Astral は、この変更によって設定済みユーザーにも有用なルールが現れる可能性があるとしている。各チームは、設定によってこのリリースが無関係になると考えるのではなく、解決後の選択を確認すべきだ。
過剰に是正するリスクもある。Ruff を固定しながら他の開発ツールをすべてフローティングのままにしておくことは、より大きな問題の目に見える一例を解決するだけだ。
チームはフォーマッター、型チェッカー、テストツール、pre-commit フック、ドキュメントビルダーを棚卸しすべきだ。それらのいずれも、クリーンなビルドを失敗へ変える可能性がある。
持続的なポリシーは単純だ。コードを出荷できるかどうかを決める環境をバージョン管理する。このポリシーには、開発者が従来は任意と分類してきたツールも含まれる。
Simon Willison と Ruff ユーザーが次に注目すべきこと
次の 3 つのシグナルは、Ruff のより広いベースラインが受け入れられるポリシーになるのか、それとも CI の摩擦源として繰り返されるのかを示す。
最初のシグナルは、バージョン 0.16.0 公開後数週間における Astral のパッチリリース活動だ。デフォルトルールへの迅速な調整は、実際のリポジトリで重大な互換性問題が発見されたことを示すだろう。
ルール固有の修正は、拡張されたベースラインを無効にするものではない。はるかに大きな選択セットが、本番ワークロードの下で調整を必要とすることを示すものだ。
逆に、ロールバック活動が限定的であれば、大半の新しい診断が対応可能だという Astral の主張を強める。さらに多くのプロジェクトが、以前のセットを復元するのではなくデフォルトを受け入れることを後押しするだろう。
2 つ目のシグナルは、公開 Python リポジトリ全体での設定動作だ。正式な調査がなくても、メンテナーはコミットを通じて判断を示すことになる。
明示的に旧デフォルトを選択する動きが広がれば、チームが即時のカバレッジより移行の制御を重視していることを示すだろう。広範な修正とデフォルト維持は、導入が成功していることを示す。
最も参考になるリポジトリは、その判断理由を文書化する。単なる ignore リストは何が変わったかを示すが、移行ノートはチームが各ルールファミリーを受け入れた、あるいは拒否した理由を説明する。
3 つ目のシグナルは、パッケージテンプレートと CI の例が Ruff を固定し始めるかどうかだ。新規プロジェクト用ジェネレーターは、後追いの警告よりも効果的に習慣を形作ることが多い。
テンプレートがバージョン制約と自動更新ワークフローを採用すれば、Willison の経験はこの単一リリースを超えて実務に影響を与えたことになる。
例が制約のない ruff のインストールを続けるなら、将来のデフォルト変更やフォーマッター変更でも同じ驚きが繰り返される可能性がある。具体的なルール数は異なっても、再現性の問題は残る。
開発者は、これらのシグナルを待ってから行動する必要はない。新しいバージョンをブランチに対して実行し、出力を保存して、どの検出結果がコードを改善するかを判断できる。
有用なテストコマンドは次のとおりだ。
バージョンを指定すれば、実験は再現可能になる。ruff format --check . を別途実行すれば、lint の失敗と Markdown またはソース整形の変更を区別しやすくなる。
まずグローバル ignore を追加してはいけない。最初に、どのルールが明確なバグを見つけたのか、どのルールがモダナイゼーションを提案するのか、どのルールが議論の余地があるポリシーを表すのかを特定する。
次に、その判断を設定とバージョン管理に記録する。どの品質契約によって得られたものか誰も分からないグリーンの CI バッジには、価値が低い。
Ruff v0.16.0 は、積極的なデフォルトの利点とコストを示している。このツールは設定なしでより多くを検出するが、設定を省略しても動作が変わらないことを意味しなくなった。
Simon Willison の経験は、この抽象的なトレードオフを差し迫ったエンジニアリング上の問いに変える。あなたのリポジトリは、リリースを制御するツールとポリシーを宣言しているだろうか。
固定したバージョンをブランチで実行し、新たなルールファミリーをすべて確認して、ベースラインを明示的にしよう。次のクリーンビルドは、CI がたまたま Ruff をインストールした日付ではなく、レビュー済みの判断を反映すべきだ。


