top of page

Simon Willison 遭遇 Ruff v0.16.0 CI 失败:默认规则变了

Ruff v0.16.0 将默认启用的 lint 规则从 59 项扩展至 413 项后,Simon Willison 发现多个 CI 任务开始失败。他未固定版本的 Ruff 开发依赖悄然将新版本引入了现有 Python 项目。

Astral 于 2026 年 7 月 23 日发布该版本。两天后,Willison 介绍了这次更新如何在未经刻意升级的情况下进入其构建流程。这些失败让一次 linter 发布成为依赖管理风险的现实警示。

核心矛盾并非更严格的 lint 与更差的代码之间的取舍,而是 Ruff 不断提升的零配置安全性,与开发者对未改动仓库应保持稳定的预期之间的冲突。只要 CI 每次运行都会安装最新可用的开发工具,这种张力就值得关注。

Ruff v0.16.0 改变了“默认”的含义

Ruff v0.16.0 最具影响力的变化不是新增命令,而是大幅扩展了未配置项目应视为错误的范围。

Ruff 是一款以 Rust 编写的 Python linter 和 formatter。Linter 会在不执行程序的情况下,分析源代码中的错误、可疑模式及部分风格问题。

在这次发布前,如果项目没有明确选择 lint 规则,Ruff 会启用 59 项规则。根据 Astral 的迁移指南,0.16.0 版本在相同条件下会启用 413 项规则。

这意味着新增了 354 项生效检查。默认安装的 Ruff 现在评估的规则数量,接近此前的七倍。

整体规则目录也在增长。Ruff 上一次在 0.1.0 版本调整默认规则时,支持 708 项规则。Astral 表示,当前规则集合已包含 968 项规则。

旧版默认规则主要选取了 Pyflakes 和 pycodestyle 的部分内容。项目无需立即对众多集成规则族作出细致选择,也能开始采用 Ruff。

这一保守基线有助于 Ruff 融入已有仓库,但也让工具实际掌握的能力与其自动报告的内容之间的差距不断扩大。

Astral 在 0.16.0 版本中弥合了很大一部分差距。新的默认规则来自更多规则族,包括 flake8-bugbear、pyupgrade 以及 Ruff 自身的 RUF 类别。

Flake8-bugbear 专注于可能存在的 bug 和值得质疑的设计模式。Pyupgrade 则识别可针对项目支持的 Python 版本进行现代化改造的语法和标准库模式。

如今的默认规则列表包含能够暴露语法问题和即时运行时错误的检查。这些并非只是对空格或命名的偏好。

这一区别解释了为何 Ruff CI 失败值得关注。一些新增报告揭示的缺陷之所以此前能够通过,只是因为 Ruff 没有自动启用相应的检测器。

另一些报告则涉及可维护性、现代化改造,或团队有意接受的模式。更大的默认规则集无法了解每个仓库的兼容性要求或设计约定。

因此,Ruff v0.16.0 同时改变了两件事:它增强了自动缺陷检测,也将更多策略决策推入 7 月 23 日之后的首次升级中。

该版本还默认对 Markdown 文件中的 Python 代码块进行格式化。支持的围栏代码块包括 pythonpypython3py3pyipycon

这一行为会影响包含文档、教程或 Quarto 笔记本的仓库。格式检查现在可以识别传统 .py 文件之外的变更。

Ruff 的发行说明还介绍了新的抑制注释和更丰富的诊断输出。这些改进有助于开发者在新增发现出现后进行处理。

范围变化仍是此次迁移的核心事件。上周还表现稳定的命令,今天面对完全相同的源代码就可能返回非零退出码。

为什么 Simon Willison 的 CI 失败值得关注

Simon Willison 的经历说明,开发工具的更新能够在不改动仓库本身的情况下,改变仓库实际执行的策略。

Willison 是一位独立开发者和作家,以 Python、数据工具和生成式 AI 相关项目闻名。他在职业生涯早期还曾共同创建 Django Web 框架。

7 月 25 日,Willison 写道,他的“多个 CI 任务”开始失败。他将问题追溯至新的 Ruff 默认规则,以及一个未固定版本的 "ruff" 开发依赖。

他的 Ruff 复盘为这次发布提供了具体用户视角。仓库代码未必出现了回归,但其验证环境却在底层发生了变化。

CI 任务,即持续集成任务,会在开发者提交或合并变更时运行自动化检查。团队依靠结果的一致性判断代码是否可以安全接纳。

如果一个任务安装 ruff 时未设置版本约束,包解析器就可能选择最新可用版本。下一次构建可能因此执行任何维护者都未明确审查过的行为。

这种失败模式很容易被忽视,因为 Ruff 通常是开发依赖。它一般不会被打包进面向用户提供服务的应用程序。

但开发依赖决定了软件能否通过交付流水线。新的 linter 退出码可能阻塞 pull request、中断发布,或耗费数小时排查。

这一事件还揭示了运行时依赖与工具之间一个颇具误导性的区分。运行时包影响已部署软件的行为,而工具则影响开发者能否部署它。

两者都可能引入运营变化,只是作用于系统的不同环节。

Willison 的经历尤其有参考价值,因为 Ruff 扩展后的规则本身按发布预期正常工作。这些失败并不需要损坏的包、被攻破的注册表或有缺陷的安装程序才会发生。

工具安装成功了。它也按照新策略正确检查了项目。CI 失败,是因为该策略不同于仓库过去隐含依赖的策略。

这使其成为一个可复现性问题。可复现的构建或检查,应当基于相同的源代码和声明输入产生等价结果。

“最新版 Ruff”并不是稳定输入,而是一个含义取决于包管理器何时解析它的动态请求。

锁文件和精确版本约束能够让该输入变得明确。随后,依赖更新服务可以提交受控升级,让维护者在合并版本变更前审查新增诊断。

这一教训并不限于 Ruff。Formatter、类型检查器、测试运行器、文档构建器和安全扫描器都可能在版本之间修改默认行为。

一个固定了应用库版本、却让开发工具版本浮动的仓库,仍然只能算部分可复现。其生产行为或许保持不变,但通往生产的路径已经改变。

这一问题对自动化编程系统尤其重要。Agent 往往会运行仓库检查、解读输出,并持续修改代码,直到通过每一道关卡。

如果这些关卡背后的工具意外变化,Agent 面对的便是一个不断移动的目标。它可能生成不必要的修改,或在不了解问题为何出现的情况下抑制发现。

构建可搜索工程知识库的团队,可以将升级决策与配置和 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 版本。

不过,恢复旧行为应当是迁移步骤,而不是自动拒绝每一项新检查。一些失败可能揭示了值得立即修复的 bug。

谨慎的升级应从获取完整诊断输出开始。维护者随后可按规则代码、严重性、修复安全性和兼容性影响对发现进行分组。

暴露确定性语法或运行时问题的规则应优先处理。机械式现代化发现可以单独审查,最好放在聚焦的提交中完成。

策略导向的检查则需要团队判断。某种模式可能对生成文件、框架约定、兼容性模块,或不能轻易变更的公共 API 而言是合理的。

Ruff 支持针对这些情况使用按文件忽略和定向抑制。0.16.0 在既有 noqa 行为之外,新增了 ruff: ignoreruff: 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 现在会在常规 checkformat --check 输出中显示建议的差异。

此前,开发者需要单独请求差异输出。新的完整输出将诊断信息与建议修改放在一起,使失败的检查更易于理解。

对于 CI 提供商而言,format --check 现在支持用于 GitHub 和 GitLab 注释的输出格式。格式问题可以直接显示在代码审查中受影响的行上。

机器消费端需要更密切地关注。Ruff 的 JSON 输出中的若干字段现在可以为 null,而不再包含占位位置。

受影响的字段包括 filenamelocationend_location,以及修复编辑中对应的位置字段。假定每个位置都是对象或字符串的消费者可能会失败。

对大多数用户而言,这是一项小型破坏性变更。但对于将 Ruff 输出解析到仪表盘、审查机器人或定制质量系统中的团队,它更为重要。

因此,更新后流水线可能在三个层面失败:Ruff 可能发现新的违规项,格式化可能扩展到新的文件类型,或者输出解析器可能拒绝可空字段。

若将每一次失败都视为“增加了更多 lint 规则”,就可能错过真正原因。维护者应先确定发生变化的是哪一层,再编辑应用程序代码。

新的抑制格式同样值得进行策略审查。行尾的 ruff: ignore[F401] 对该诊断的作用类似于定向的 noqa

前置注释可以抑制下一逻辑行上的发现。对于多行函数头很有用,因为被报告的问题未必能整齐地放在相关 token 旁边。

通过 ruff: file-ignore 可以进行文件级抑制。它可以包含原因说明,比没有解释的全面排除能为审查者提供更多信息。

新的 --add-ignore 选项可以自动插入抑制项。这种便利不应取代对底层诊断是否代表真实缺陷的审查。

自动化可以通过添加注释迅速让 CI 变绿,但它无法判断一个项目是否应该将该例外保留多年。

Ruff 将被视为安全的修复与需要启用不安全修复选项的修复区分开来。即使被归类为安全,也应结合生成代码、公共 API 和异常运行时行为进行审查。

Python 的动态特性限制了静态分析能够保证的范围。Ruff 自己的修复指南要求用户报告安全修复损害代码的情况。

这一限制并不会削弱 lint 的价值,而是强化了区分检测、自动修改与人工批准的必要性。

未固定版本的开发工具如今是压力点

眼前的压力主要落在动态安装 Ruff、同时又未明确选择规则的仓库上。

一个完全固定 Ruff 版本且具有明确 select 列表的项目,拥有两项稳定控制:一项固定工具实现,另一项固定项目所选定的 lint 策略。

使用浮动版本但明确指定规则的项目具有部分稳定性。新的 Ruff 版本仍可能改变单项规则行为、解析、输出、格式化或配置语义。

固定版本但未明确指定规则的项目同样只有部分稳定性。CI 会保持一致,直到维护者更新 Ruff,届时默认规则迁移将一次性到来。

既未固定版本、也未明确指定规则的项目则两项控制都没有。这种组合造就了 Simon Willison 所遭遇 Ruff CI 失败的条件。

固定版本并不意味着无限期冻结工具。它将“发现升级”和“采用升级”分离开来。

依赖更新拉取请求会创建清晰可见的审查边界。当现有主分支仍保持可复现时,CI 可以显示新的发现。

维护者随后可以在多种应对方式中选择:

  • 修复新默认规则识别出的明确缺陷。

  • 在独立提交中接受安全的机械性修改。

  • 为仓库特有模式配置有意设定的例外。

  • 恢复此前的规则选择,并安排分阶段采用规则。

  • 更新无法处理可空 JSON 位置字段的解析器。

  • 排除必须保留手工格式的文档文件。

这些行动不应被盲目混在一起。一次大型自动修复提交可能会在数千处格式化编辑中掩盖行为变化。

按规则族分组处理可以带来更清晰的审查。如果某条规则与项目支持的 Python 版本冲突,也更容易回滚。

当 pyupgrade 规则处于启用状态时,目标版本配置很重要。现代语法对于某个解释器基线可能正确,却可能无法用于另一个基线。

团队应验证 Ruff 配置的 Python 目标版本是否与实际部署环境一致。否则,现代化建议可能会领先于生产环境支持。

生成代码也需要单独处理。对生成文件重新格式化或执行 lint,往往会产生在下次运行生成器时消失的变更。

排除生成路径通常比用抑制注释填满它们更准确。生成器的源代码或模板通常才是落实质量要求的合适位置。

单体仓库还面临另一项复杂因素:不同包可能支持不同的 Python 版本,或维护不同的 lint 策略。

根级默认设置可以简化运维,但也可能将同一迁移节奏强加给彼此无关的组件。按包配置可能更能反映所有权关系。

主要的质疑在于,413 条规则是否构成普遍可接受的基线。Astral 已记录了这一选择,但实际采用将检验其误报率和兼容性负担。

Willison 的失败提供了一个早期信号,而非具有代表性的调查。它们表明扰动是可能的,并不意味着大多数 Ruff 用户都会经历这种情况。

已经使用明确 selectextend-select 的项目可以采取不同的应对方式。其实际规则集取决于该配置与新基线的交互方式。

Astral 表示,这项变化仍可能为已配置的用户呈现有用规则。每个团队都应检查最终解析出的规则选择,而不是假定配置会让该版本变得无关紧要。

还存在过度纠正的风险。固定 Ruff 版本、同时让其他所有开发工具保持浮动版本,只解决了更广泛问题中的一个明显实例。

团队应盘点格式化工具、类型检查器、测试工具、pre-commit 钩子和文档构建器。其中任何一个都可能将原本通过的构建变成失败。

持久的策略很简单:对决定代码是否可以发布的环境进行版本控制。这一策略也包括开发者传统上视为可选的工具。

Simon Willison 和 Ruff 用户接下来应关注什么

接下来的三个信号将显示,Ruff 更广泛的基线会成为被接受的策略,还是持续造成 CI 摩擦的来源。

第一个信号是 Astral 在 0.16.0 版本发布后数周内的补丁版本活动。若对默认规则进行快速调整,将表明真实仓库发现了显著的兼容性问题。

针对特定规则的修正不会否定扩展后的基线。它们会表明,更大的规则选择需要在生产工作负载下进行调优。

相反,有限的回滚活动将强化 Astral 的观点,即大多数新诊断都可付诸行动。这也会鼓励更多项目接受默认规则,而不是恢复此前的集合。

第二个信号是公共 Python 仓库中的配置行为。即使没有正式调查,维护者也会通过提交展现其判断。

若出现一波明确选择旧默认规则的情况,将表明团队更重视迁移控制,而非立即扩大覆盖范围。广泛修复问题并保留默认规则,则意味着采用较为成功。

最具参考价值的仓库会记录其理由。简单的忽略列表只能显示发生了什么变化,而迁移说明会解释团队为何接受或拒绝每个规则族。

第三个信号是包模板和 CI 示例是否开始固定 Ruff 版本。新项目生成器往往比事后警告更有效地塑造习惯。

如果模板采用版本约束和自动更新工作流,Willison 的经历将影响这次单一发布之外的实践。

如果示例仍继续安装没有约束的 ruff,未来的默认规则或格式化工具变化仍可能带来同样的意外。具体规则数量会不同,但可复现性问题仍将存在。

开发者无需等待这些信号再采取行动。他们可以在分支上运行新版本,保留输出,并决定哪些发现能够改善代码。

一个有用的测试命令是:

指定版本会让实验可复现。单独运行 ruff format --check . 有助于区分 lint 失败与 Markdown 或源代码格式化变更。

不要一开始就添加全局忽略项。先识别哪些规则发现了明确的 bug,哪些建议进行现代化,哪些则体现了有争议的策略。

随后将决定记录在配置和版本控制中。当没人知道是由哪份质量契约产生时,绿色 CI 徽章的价值就会降低。

Ruff v0.16.0 展现了活跃默认规则的优势与代价。该工具无需配置便能发现更多问题,但省略配置已不再意味着行为保持不变。

Simon Willison 的经历将这一抽象权衡转化为直接的工程问题:你的仓库是否声明了控制其发布的工具与策略?

在分支上运行固定版本,检查每个新增规则族,并明确基线。下一次干净构建应反映经过审查的决定,而不是 CI 恰好安装 Ruff 的日期。

 
 

免费开始

一款本地优先的AI助手,具备个人知识管理功能

为了获得更好的人工智能体验,

remio 目前仅支持Windows 10+ (x64)M-Chip Mac

在你的大脑里添加一个搜索栏

Ask remio

记住一切

​无需整理

bottom of page