mcpField
P
python-precommit

python-precommit

github.com/paldom测试与质量348 次安装8 周前更新
代码仓库

关于 python-precommit

用于在 Python 仓库中配置和维护 pre-commit 的技能,重点是避免失效的钩子、保持本地检查与 CI 一致,并控制运行速度和版本固定。

主要功能

  1. 01检查仓库现有配置
  2. 02固定钩子版本并同步工具版本
  3. 03区分快速提交检查与推送检查
  4. 04验证并格式化非 Python 文件
  5. 05在 CI 中复用钩子配置
  6. 06只读检查配置问题

适用场景

  1. 01为 Python 仓库添加 pre-commit
  2. 02排查与 CI 不一致的钩子
  3. 03缩短提交检查耗时
  4. 04检查钩子配置中的常见陷阱

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/paldom/python-skills --skill python-precommit

安装后在 Claude Code 中输入/python-precommit确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
config-recipes.md
sync-and-ci.md
scripts/
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。

操作原则

  1. 钩子是为了方便;CI 才负责强制执行。 git commit --no-verify 可以悄无声息地绕过所有检查,且不留审计记录。绝不要把钩子说成保证。 .pre-commit-config.yaml 中的每项检查也必须在 CI 中运行 (switowski.com/blog/pre-commit-vs-ci)。
  2. 提交阶段必须保持快速——只需几秒。缓慢的钩子是养成 --no-verify 习惯的主要原因。耗时较长的检查应放到 pre-push 或 CI 中。
  3. 固定所有版本。 每个 repo: 都必须有固定的 rev:;未固定版本或固定到分支的钩子既无法复现,也是供应链安全漏洞(安装钩子时会克隆并执行任意仓库—— pre-commit#942)。
  4. 绝不要启用会在现有代码树上失败的钩子。 先对所有文件运行检查并修复问题,提交后再让它开始拦截。

工作流

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];发现问题时退出码非零)。

评论

支持 Markdown