关于 python-precommit
用于在 Python 仓库中配置和维护 pre-commit 的技能,重点是避免失效的钩子、保持本地检查与 CI 一致,并控制运行速度和版本固定。
主要功能
- 01检查仓库现有配置
- 02固定钩子版本并同步工具版本
- 03区分快速提交检查与推送检查
- 04验证并格式化非 Python 文件
- 05在 CI 中复用钩子配置
- 06只读检查配置问题
适用场景
- 01为 Python 仓库添加 pre-commit
- 02排查与 CI 不一致的钩子
- 03缩短提交检查耗时
- 04检查钩子配置中的常见陷阱
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paldom/ python-skills --skill python-precommit安装后在 Claude Code 中输入/python-precommit确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/config-recipes.mdsync-and-ci.mdscripts/check_precommit_config.py详细介绍
python-precommit
在用户的 Python 仓库中设置并维护 pre-commit 框架。价值不在于“编写 YAML 文件”——有能力的模型都能做到。真正的价值在于避开那些会让钩子配置悄无声息地失效的具体陷阱:钩子版本未固定或已漂移,导致与 CI 不一致;ruff 钩子自动修复后仍放行提交;mypy 钩子在空环境中运行;已归档的 Prettier 镜像;以及运行缓慢、让所有人都养成输入 --no-verify 习惯的钩子。
不适用场景
- Claude Code / Agent 钩子(PostToolUse、settings.json、AGENTS.md 规则、应该由哪个执行层运行哪些内容)——如果已安装,由 agent-guardrails skill 负责。
- 除下方单个 pre-commit 镜像任务之外的 CI 工作流编写(矩阵、必需检查、缓存、大规模固定 Action SHA)——使用 python-ci skill。
- 选择 ruff 规则或修复 lint/格式问题——使用 python-lint skill。
- 选择或配置类型检查器本身——使用 python-typing skill。
- 使用 conventional commits 的发布自动化(版本升级、变更日志、发布)——使用 python-release skill。此 skill 仅在提交时强制执行消息格式。
- 仓库级别的密钥扫描 / 推送保护 / 针对包依赖项的 Dependabot——使用 python-supply-chain skill。
操作原则
- 钩子是为了方便;CI 才负责强制执行。
git commit --no-verify可以悄无声息地绕过所有检查,且不留审计记录。绝不要把钩子说成保证。.pre-commit-config.yaml中的每项检查也必须在 CI 中运行 (switowski.com/blog/pre-commit-vs-ci)。 - 提交阶段必须保持快速——只需几秒。缓慢的钩子是养成
--no-verify习惯的主要原因。耗时较长的检查应放到pre-push或 CI 中。 - 固定所有版本。 每个
repo:都必须有固定的rev:;未固定版本或固定到分支的钩子既无法复现,也是供应链安全漏洞(安装钩子时会克隆并执行任意仓库—— pre-commit#942)。 - 绝不要启用会在现有代码树上失败的钩子。 先对所有文件运行检查并修复问题,提交后再让它开始拦截。
工作流
1. 检查仓库
编写配置前,先检查:
ls .pre-commit-config.yaml .git/hooks/pre-commit 2>/dev/null # existing setup?
grep -n 'ruff\|mypy' pyproject.toml # versions to keep hooks in lockstep with
ls .github/workflows/ 2>/dev/null # workflows -> add check-jsonschema; CI mirror target
ls package.json 2>/dev/null # Node already present? -> Prettier is cheap here
记录开发依赖组中的 ruff(和 mypy)版本——钩子版本必须与之匹配,否则本地和 CI 的结果会不一致(即第 7 步中的“双重核算”问题)。
2. 编写 Python 基线配置
在仓库根目录创建 .pre-commit-config.yaml。固定每个 rev(下方示例使用标签;有关固定 SHA,请参阅第 7 步),然后运行一次 pre-commit autoupdate 以解析当前发布版本并检查差异——下方的 rev 是已知可用的示例,并非最新版本。
# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, commit-msg, pre-push]
repos:
# Hygiene — cheap, universal
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-toml
- id: check-json
- id: check-added-large-files
- id: check-merge-conflict
- id: detect-private-key
# Python lint + format. Order matters: fixer first, formatter second.
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.6 # keep in lockstep with the ruff version in your dev deps
hooks:
- id: ruff-check # named `ruff` on older revs
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
两个关键细节:
- pre-commit 会将任何修改过文件的钩子判为失败(“files were modified by this hook”),因此自动修复的结果绝不会在未暂存的情况下混入提交;“失败 →
git add→ 重新提交”的循环是预期行为——向用户解释一次即可。 在 pre-commit 下,--exit-non-zero-on-fix没有额外作用;当同一条ruff check --fix在其外部运行时(例如 Makefile 或 CI 步骤),它才有意义,因为否则修复文件后会以 0 退出,看起来像是检查通过。 - pre-commit 中的排除规则有其特殊之处:pre-commit 会显式传递文件名,这可能导致忽略排除子目录中
pyproject.toml排除规则的情况 (ruff#9585)。 请将排除规则放在 ruff 配置的顶层,并为确保生效,在 pre-commit 配置中添加钩子级别的exclude: ^(vendored|migrations)/正则表达式。
3. 非 Python 文件——格式化并验证
格式化不等于验证:格式错误的 GitHub 工作流或 pyproject.toml
依然能通过格式化。添加这两个层次,并根据工具链选择格式化方案,而不是凭习惯:
- 存在
package.json(Node 已是一等公民)→ 使用维护中的分支版 Prettier——原版pre-commit/mirrors-prettier已归档,从 Prettier v3 起就无法正常工作;较早的教程仍会指向它。 - 纯 Python 仓库 → 优先采用不依赖 Node 的工具栈(Markdown 用 mdformat,TOML 用 taplo,YAML 用 yamllint——工具更多,但完全不需要 npm;方案和权衡见 references/config-recipes.md)。 Prettier 钩子的运行环境会自行引导安装 Node,而这个引导过程(首次运行时下载、代理、特殊架构)是“prettier 钩子一直报错”反馈的首要来源。若团队希望用一个格式化工具处理 YAML、Markdown 和 JSON,仍可选择 Prettier——请说明其中的权衡,并参阅 gotchas 中的引导修复方法。
Prettier 方案:
- repo: https://github.com/rbubley/mirrors-prettier # maintained fork
rev: v3.9.6
hooks:
- id: prettier
types_or: [yaml, markdown, json] # never .py/.pyi — no fight with ruff
args: [--prose-wrap=always]
# Ruff >= 0.16 formats Python code fences inside Markdown by default; Prettier
# leaves fence contents alone, so the two coexist — but do not add a second
# Python formatter for Markdown.
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.26
hooks:
- id: validate-pyproject
additional_dependencies: ["validate-pyproject-schema-store[all]"]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.38.0
hooks:
- id: check-github-workflows
- id: check-dependabot
验证器(validate-pyproject、check-jsonschema)适用于这两种方案; yamllint 与 Prettier 之间的冲突见 references/config-recipes.md。
4. 提交消息阶段——约定式提交
仅当用户希望强制执行提交格式时才添加(提交格式会供变更日志和发布自动化使用,这部分由 python-release 技能负责):
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
只有安装了 commit-msg 钩子类型,commit-msg 钩子才会运行——
default_install_hook_types(已在基线配置中)会处理这一点。若没有此配置,普通的 pre-commit install 只会安装 pre-commit 阶段,检查会被悄然跳过。
5. 推送前阶段——耗时检查
mypy 和测试对于每次提交来说都太慢。通过使用项目自身环境的 repo: local 钩子,在每次推送时运行一次(这也避开了 mirrors-mypy 的隔离环境问题——见 gotchas):
- repo: local
hooks:
- id: mypy
name: mypy (pre-push)
entry: uv run mypy .
language: unsupported
pass_filenames: false
stages: [pre-push]
- id: pytest
name: pytest (pre-push)
entry: uv run pytest -q
language: unsupported
pass_filenames: false
stages: [pre-push]
language: unsupported(pre-commit >= 4.4 中的名称;较早版本使用 system)
表示工具必须存在于每个克隆中——这里没问题,因为 uv run 会解析项目锁定的版本(pip 备选方案:entry: python -m mypy .)。
时间预算:推送前检查应少于约 30 秒;更慢的检查只能放在 CI 中。
6. 安装并首次运行
uv add --dev pre-commit # pins pre-commit itself in uv.lock
uv run pre-commit install # installs every stage in default_install_hook_types
uv run pre-commit run --all-files
(不使用 uv 时:python -m pip install pre-commit,然后运行相同的 pre-commit …
命令——此处只说明一次,适用于本技能中的所有命令。)
首次运行 run --all-files 会比较慢(钩子运行环境会按需构建),并且在修复文件时很可能会失败——重复运行,直到检查通过;审查后,将格式化改动与配置分开提交。钩子针对每个克隆单独安装,不受 git 跟踪,因此要将安装步骤写入贡献者文档或 make setup 目标;对于从未运行过安装步骤的克隆,只有 CI 能兜底。
7. 在 CI 中同步并保持版本一致
本地钩子和 CI 必须使用相同配置和相同版本。镜像任务(此处只涉及这一个任务;其他 CI 内容归 python-ci 技能负责):
# .github/workflows/pre-commit.yml
name: pre-commit
on:
pull_request:
push: { branches: [main] }
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7 # pin actions to full SHAs in real use
- uses: actions/setup-python@v7 # (re-tagged releases are a real attack)
with: { python-version: "3.12" }
- run: python -m pip install pre-commit==4.6.2 # match your uv.lock version
- run: pre-commit run --all-files --show-diff-on-failure
由于 CI 运行的是完全相同的 .pre-commit-config.yaml,钩子的 rev 不会在本地和 CI 之间发生偏移。仍可能不一致的是钩子 rev 与开发依赖组中同一工具的版本(两处都配置 ruff 是典型例子)。请保持二者同步;可选择以下方式之一自动更新 rev:Dependabot 的 pre-commit 生态系统、pre-commit.ci 的定期 autoupdate,或每月提交一个 pre-commit autoupdate PR(经过审查,绝不盲目接受)。对于安全敏感型仓库,pre-commit autoupdate --freeze 会将 rev 固定为完整提交 SHA,避免标签被重新指向。详情、关于 pre-commit.ci 自动修复的讨论,以及 pre-commit/action 仅用于维护的状态,见
references/sync-and-ci.md。
8. 验证
python3 "${CLAUDE_SKILL_DIR}/scripts/check_precommit_config.py" .pre-commit-config.yaml # footgun lint, read-only
uv run pre-commit run --all-files # exits 0 on a clean tree
git commit --allow-empty -m "bad msg" # commit-msg hook must reject (if step 4 used)
此脚本(位于此技能的文件夹中)会检查未固定的 rev、已归档的
Prettier 镜像、缺少 --exit-non-zero-on-fix、没有
additional_dependencies 的 mirrors-mypy、未限定范围的 Prettier、错误的钩子顺序,以及已配置但从未安装的阶段。脚本不会修改任何内容。
输出规范 — 完成标准
- 仓库根目录下有
.pre-commit-config.yaml;每个远程仓库都有固定的rev(至少为标签;适用时通过--freeze固定 SHA)。 default_install_hook_types涵盖所有钩子使用的阶段。- 在当前代码树上运行
pre-commit run --all-files,退出码为 0。 - 典型提交只需几秒即可完成钩子检查。
- CI 运行相同的配置(
pre-commit run --all-files任务或 pre-commit.ci)——没有单独调用不同版本的 ruff/mypy 来重复运行钩子。 - 存在更新 rev 的机制(Dependabot / pre-commit.ci / 定期安排的 autoupdate PR)。
- 贡献者文档或 Makefile 中提及
pre-commit install。 scripts/check_precommit_config.py报告 0 个错误。
故障模式与注意事项
- Ruff「修复」了文件,但提交仍包含旧内容
→ ruff 钩子的参数中缺少
--exit-non-zero-on-fix(第 2 步)。 - 提交失败,开发者困惑不解,文件「已经修复」了 → 自动修复钩子
按设计会让修复保持未暂存状态;运行
git add -u,然后重新提交。在 贡献者文档中说明一次即可。 - 本地通过,CI 失败(反之亦然) → 版本不一致:钩子的
rev与开发依赖项或 CI 安装的版本不一致。修复方法是在 CI 中运行相同的配置,并同步 各处的版本(第 7 步)。这种「双重记账」是反复出现的首要 pre-commit 问题。 - Ruff 检查了其排除列表中的文件 → pre-commit 会显式传入路径;嵌套配置中的
排除项可能因此被绕过
(ruff#9585)。使用顶层
排除项和钩子级别的
exclude:正则表达式。 - mypy 钩子报告导入错误/漏掉实际错误
→
pre-commit/mirrors-mypy在隔离环境中运行,不包含项目的依赖项,而且只能看到已暂存的 文件(无法查看整个程序)。可以添加additional_dependencies: [types-…, pydantic, …]并接受不完整的 检查;或者优先使用本地的uv run mypy .推送前钩子(第 5 步)。 - Prettier 钩子损坏或卡住 →
pre-commit/mirrors-prettier已归档;请使用rbubley/mirrors-prettier。如果仍在安装钩子环境时报错 (Node 下载失败——代理、离线机器或特殊架构所致),可在 prettier 钩子中添加language_version: system,以复用机器自带的 Node(此时所有环境,包括 CI,都必须安装 Node);或者让纯 Python 仓库改用不依赖 Node 的工具栈(第 3 步)。 - Agent 编写的更改总是在所有者提交时未能通过这些钩子
(Prettier 重新格式化、空白修复器触发)→ 这是 Agent 层的配置未对齐,而非 pre-commit 的问题:Agent
的 Stop 门禁必须在确认完成前运行同一配置(
pre-commit run --files <changed>)—— agent-guardrails skill 已提供该门禁。绝不要让 Agent 重新实现 部分提交门禁;各个子集会逐渐偏离。 - Prettier 和 ruff 争抢文件 → 始终使用
types_or: [yaml, markdown, json]限定 Prettier 的处理范围。注意,TypeScript 的类型 标识符是ts,而不是typescript——名称错误时不会匹配任何内容,且不会发出提示。 - yamllint 未通过 Prettier 刚格式化的文件 → 它们的默认配置确实冲突(注释间距、缩进);请显式协调配置 (见 references/config-recipes.md),或移除其中一个工具。
- 钩子互相冲突,陷入反复重新格式化 → 顺序应为修复器 →
格式化器(先运行
ruff-check --fix,再运行ruff-format);格式化器应放在最后。 - 已配置 commit-msg/pre-push 钩子却从未触发 → 从未安装该钩子类型。请使用
default_install_hook_types,或运行pre-commit install --hook-type commit-msg --hook-type pre-push。 - 新贡献者完全没有钩子 → 按设计,每个克隆都需要单独安装。 请在文档中说明;依靠 CI 镜像作为后备保障。
- 所有人都用
--no-verify绕过钩子 → 钩子太慢或产生太多干扰;这是设计失败,而非纪律问题。使用pre-commit run --verbose分析耗时,将耗时较长的钩子移到推送前或 CI 中,并提供SKIP=<hook-id> git commit …作为有针对性的替代方案。不要承诺 任何本地机制都能阻止绕过。 - 首次运行时会下载所有内容 → 这是预期行为;之后环境会缓存到
~/.cache/pre-commit。提醒用户一次即可。 - 在 pre-commit 阶段设置覆盖率阈值或运行完整测试套件 → 社区普遍认为这些检查应该放在 CI 中(或至多放在推送前);提交时的覆盖率门禁会被绕过,也会招致反感。
尚无定论的争议——列出选项,不要断言哪种方案胜出
- 在 CI 中自动修复——主流观点:CI 负责检查,不修改文件
(
pre-commit run --all-files,有差异时失败)。pre-commit.ci 的自动修复功能 (自动将格式化结果提交到 PR)是部分团队会采用的有意例外; 需注意,已有记录表明,该功能无法自动修复.github/workflows文件。 - prek(github.com/j178/prek)——基于 Rust,
由 uv 驱动,可直接读取相同的
.pre-commit-config.yaml;安装和更新速度更快 (据社区反馈),并原生支持 monorepo;但它较新,实战检验较少(截至 2026-09 为 0.5.x)。 命令已更名:install-hooks→prepare-hooks、autoupdate→update、SKIP→PREK_SKIP;prek 专属配置扩展可能会让预期纯 pre-commit YAML 的审查工具出错。 若速度或 monorepo 支持成为问题,可以考虑采用:uv add --dev prek && uv run prek install。 - “Agent 时代 pre-commit 已死”——少数观点(Massdriver 2026 年年中演讲)认为,Agent 高频编辑让本地钩子不切实际,应将检查集中到 CI。 当前大多数技术栈仍将 pre-commit 作为快速的本地检查层;如果用户的代码仓库中 Agent 使用较多, 应说明其中的权衡,让他们自行选择。每项检查由哪一层(Agent 钩子、git 钩子还是 CI)执行,属于 agent-guardrails 技能的职责范围。
附带材料
- references/config-recipes.md——完整的注释配置: 完整基线、Prettier 与不依赖 Node 的技术栈、校验器、 conventional-commit 和 pre-push 配置、uv 锁文件钩子、可选的密钥扫描、 Markdown 中的 ruff、monorepo/本地钩子模式。
- references/sync-and-ci.md——重复记账问题、 CI 镜像配置、pre-commit.ci、rev 更新自动化、 SHA 固定与钩子供应链、诚实说明绕过方式、prek、阶段分配预算。
- scripts/check_precommit_config.py——
针对现有
.pre-commit-config.yaml的只读风险配置检查工具 (python3 "${CLAUDE_SKILL_DIR}/scripts/check_precommit_config.py" [path] [--strict];发现问题时退出码非零)。