mcpField
P
python-ci

python-ci

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

关于 python-ci

为 Python 仓库配置并加固 GitHub Actions 质量门禁的技能,供维护仓库 CI 的开发者使用。它涵盖版本矩阵检查、统一汇总状态、分支规则和工作流安全验证。

主要功能

  1. 01配置 lint、类型检查与测试
  2. 02按 Python 版本构建矩阵
  3. 03汇总稳定的必需检查
  4. 04接入覆盖率合并检查
  5. 05加固 Actions 与令牌权限
  6. 06验证工作流和分支规则

适用场景

  1. 01为 Python 仓库建立 CI 门禁
  2. 02将测试矩阵接入合并队列
  3. 03防止跳过任务导致检查误通过

安装

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

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

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

包含的文件

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

evals/
evals.json
references/
ci-workflow-template.md
required-checks-and-rulesets.md
workflow-hardening.md
scripts/
check_workflows.py

详细介绍

python-ci

用途

为 Python 仓库构建 GitHub Actions 质量门禁:工作流在多个 Python 版本矩阵中运行 lint、类型检查和测试,并通过 uv 缓存,将结果汇总到一个 all-checks-passed 聚合任务中;将其设为分支规则集中的必需状态检查;并防范 CI 被悄然削弱的各种方式(被跳过但仍被视为成功的任务、导致 PR 无法合并的重命名矩阵分支、可变的操作标签、权限过宽的 GITHUB_TOKEN、pull_request_target 和缓存信任边界漏洞)。CI 是唯一无法通过 --no-verify 绕过的执行层——应将工作流文件本身视为生产代码。

不适用场景

  • 发布工作流(由标签触发的 PyPI 发布、可信发布、版本升级)——如果已安装,python-release 技能负责这些工作。本技能只确保发布任务可以通过 needs: 依赖质量门禁。
  • pre-commit 配置或其 CI 镜像任务——使用 python-precommit 技能。
  • coverage.py 配置(fail_under 值、relative_files、源路径)——使用 python-testing 技能。本技能只负责在工作流中接入覆盖率(每个分支的制品、合并任务的位置)。
  • Dependabot、密钥扫描、CodeQL、Scorecard、SBOM、CODEOWNERS——使用 python-supply-chain 技能。
  • 修复 CI 报告的问题(lint 错误、类型错误、测试失败)——使用 python-lint / python-typing 技能,或进行常规调试。

工作流

1. 编写 YAML 前先检查仓库
  • 阅读 pyproject.toml:查看配置了哪些工具([tool.ruff]、mypy/pyright/ty、pytest)、requires-python 范围(它决定版本矩阵),以及依赖组(dev 或 dependency-groups)。
  • 检查是否提交了 uv.lock。如果有,CI 必须使用 uv sync --locked——绝不能在运行器中重新生成锁文件;锁文件的更改应通过提交完成。
  • 列出已有的 .github/workflows/ 文件;有针对性地扩展或替换,并检查仓库设置中是否启用了合并队列(它会改变触发器集合——见第 5 步)。
  • CI 应运行仓库已经定义的内容。不要引入新工具或更宽松的变体:门禁必须运行开发者本地使用的相同命令,并且处于检查模式(ruff check、ruff format --check)——如果前一层会自动修复,CI 只负责验证。不得将会运行 --fix 的 make check 复用为 CI 步骤。
  • 在现有代码库中首次运行 CI:先在本地运行 lint、类型检查和测试命令。在从未进行过 lint 的仓库中启用门禁会直接失败;应先提交清理代码的提交(或交由相关技能处理),再启用门禁。
2. 编写质量门控工作流

创建 .github/workflows/ci.yml。包含 coverage 和 aggregator 作业并已连通的完整注释模板:references/ci-workflow-template.md。 关键选择:

name: CI
on:
  push:
    branches: [main]
  pull_request:
  merge_group:          # without this, merge-queue validation silently never reports

permissions:
  contents: read        # job-level blocks widen this only where needed

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
  • 作业:lint(ruff check + format --check)、typecheck(仓库配置的类型检查器)和 test(在矩阵中运行 pytest)。有两种有效的顺序,选一种并说明原因:三者并行(实际耗时最短,反馈最快),或 test: needs: lint(lint 在 10 秒内失败时可跳过耗时较长的矩阵;lint 通过时反馈较慢)。对于小型测试套件,并行是更稳妥的默认选择。
  • 环境:使用 astral-sh/setup-uv,设置 enable-cache: true,并传入 python-version: ${{ matrix.python-version }}——无需单独使用 actions/setup-python。使用 uv sync --locked --group dev 安装,使用 uv run <tool> 运行工具。(没有 uv?可使用备用方案:actions/setup-python@v7 并设置 cache: pip,再运行 pip install -e . --group dev(pip >= 25.1 可读取 PEP 735 组),然后直接运行工具命令;说明一次即可,继续下文。)
  • 矩阵:包含 requires-python 中的每个版本,并加引号——['3.11', '3.12', '3.13', '3.14'](3.10 将于 2026-10 EOL;3.15 将于 2026-10-01 发布)。不加引号的 3.10 在 YAML 中会被解析为浮点数 3.1。fail-fast 的选择规则:需要完整的跨版本反馈时设为 false(此处默认如此);只有在快速 PR 循环中,首个失败已足够时才设为 true。仅当软件包执行依赖平台的工作时才添加 OS 轴;每个轴都会增加计费分钟数(每次工作流运行最多 256 个作业)。
  • 从第一稿起,就将每个 action 固定到完整的提交 SHA(步骤 6),不要留到之后再处理。
3. 在矩阵中接入 coverage(仅工作流侧)

如果仓库会测量 coverage,逐个矩阵分支进行门控是错误的:即使合并后的 coverage 达标,某个分支未覆盖特定版本代码也会导致失败。

  • 每个矩阵分支运行 pytest --cov --cov-fail-under=0,并设置 COVERAGE_FILE=.coverage.py${{ matrix.python-version }}(pytest-cov 会为每个分支写入一个 .coverage 文件;除非明确关闭,否则会按每个分支应用配置中的 fail_under),然后将其上传为 artifact:名称须包含矩阵值且唯一,并设置 include-hidden-files: true,因为 .coverage.* 文件是隐藏文件。
  • coverage 作业(needs: test)下载所有分支的文件,运行 uv run coverage combine 和 uv run coverage report。阈值数值和 relative_files = true(跨运行器合并所必需)应放在仓库的 coverage 配置中——这属于 python-testing 范畴;本技能只负责在 combine 作业中设置门控。
  • 要上传到外部服务(如 Codecov)?从 combine 作业或一个指定的分支上传——每个分支都上传会产生重复或冲突的报告。
4. 添加 aggregator 作业——唯一必需的检查

矩阵分支和作业名称会变化;分支设置不会随之更新。使用一个稳定的作业进行门控:

  all-checks-passed:
    runs-on: ubuntu-latest
    needs: [lint, typecheck, test, coverage]
    if: always()        # without this the job is SKIPPED when a dep fails — and skipped counts as success
    steps:
      - name: Fail if any needed job did not succeed
        run: |
          if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}" == "true" ]]; then
            echo "::error::A required job failed, was cancelled, or was skipped."
            exit 1
          fi
          echo "All checks passed."
  • 测试中包含 skipped 是有意为之:如果某项依赖的 if: 配置错误,会显现出来,而不是悄悄通过。只有在某些作业有意按路径筛选时,才去掉它——并说明原因(参考内容包含按路径筛选的变体)。
  • 保持作业名称稳定;重命名意味着必须在同一个 PR 中更新分支设置,否则无法合并。
5. 设为必需项——规则集或分支保护

先运行一次工作流(推送分支/打开测试 PR):检查项至少报告过一次后,才会出现在必需检查的选择器中。

优先使用规则集(可叠加、也能针对标签、适用于组织级别、带有“Evaluate”预览模式,并支持细粒度配置绕过者);对于单一且简单的仓库,经典分支保护也可以。通过 API 配置以避免配置漂移——经典方式:

# --field/-F send strings (and coerce bools/ints), never nested objects — give the
# API a JSON body instead.
gh api repos/{owner}/{repo}/branches/main/protection --method PUT --input - <<'JSON'
{ "required_status_checks": { "strict": true, "contexts": ["all-checks-passed"] },
  "enforce_admins": true,
  "required_pull_request_reviews": { "required_approving_review_count": 1, "dismiss_stale_reviews": true },
  "restrictions": null }
JSON
  • 只将 all-checks-passed 标记为必需项。将单个矩阵任务设为必需检查会在矩阵变更时卡住每个 PR(“等待报告状态”)。
  • 启用“禁止绕过”/保持 enforce_admins 开启——否则,管理员(或持有管理员令牌的 Agent)可以绕过所有检查直接合并。
  • 在繁忙的主分支上设置 strict: true(分支必须保持最新)会造成反复变基的恶性循环;结构性的解决方案是合并队列。使用合并队列时,工作流必须在 merge_group 事件上触发,并在入队时验证必需检查。
  • 标签保护(阻止重新为发布版本打标签)是规则集功能;如果仓库会发布构件,请添加标签规则集。

规则集 JSON、合并队列的具体设置,以及针对“PR 检查全绿但无法合并”的诊断指南:references/required-checks-and-rulesets.md。

6. 加固工作流

详情、理由和事件记录:references/workflow-hardening.md。

  • 为每个操作固定 SHA——可变标签可能会被重新指向(2025 年 tj-actions 泄露事件向所有使用 @v 的用户分发了恶意代码)。解析标签:

    git ls-remote https://github.com/astral-sh/setup-uv refs/tags/v10\*
    # the `^{}` line is the commit SHA for annotated tags — pin that one
    

    格式:uses: owner/action@<40-hex-sha> # v10.0.1——保留版本注释; 请注意,astral-sh/setup-uv 在 8.0.0 版本中删除了浮动的主版本/次版本标签,因此 @v10 根本无法解析——只有完整版本或 SHA 才有效; 更新工具和人工维护者都会依据此注释。

  • 尽量减少 GITHUB_TOKEN 权限:在工作流级别设置 permissions: contents: read, 仅在需要时于任务级别添加权限。切勿依赖仓库范围的默认设置。

  • 使用 zizmor 对工作流进行静态分析(模板注入、危险触发器、权限过多),并固定版本:

    uvx zizmor==1.30.1 .github/workflows/
    

    查看 https://github.com/zizmorcore/zizmor 获取当前版本,并有意更新固定版本。actionlint(PyPI:actionlint-py,固定到你验证过的版本)还可发现格式化工具不会检查的结构/语法错误。

  • 信任边界:除非完全了解 pull_request_target,否则应避免使用——如果同时检出 PR 代码,或允许缓存读写,就会带来实时的机密外泄和缓存投毒风险。Actions 缓存在每个仓库的命名空间内共享:绝不要缓存机密,并将可由 fork PR 写入的缓存视为后续受信任任务的不可信输入。

  • 运行 scripts/check_workflows.py(见下文),以确定性地捕获上述问题的回归。

7. 端到端验证
python3 "${CLAUDE_SKILL_DIR}/scripts/check_workflows.py" --repo .        # unpinned actions, float versions, missing if: always(), ...
uvx zizmor==1.30.1 .github/workflows/               # security findings
git switch -c ci-setup && git push -u origin ci-setup
gh pr create --fill && gh pr checks --watch        # every job green, aggregator reports

然后确认 all-checks-passed 可选且已在分支规则中选中;如果启用了合并队列,还要确认已入队的 PR 确实能够合并。

输出规范——完成后的样子

  • .github/workflows/ci.yml:lint + typecheck + test-matrix +(coverage)+ all-checks-passed;触发器包括 pull_request 和 merge_group; 工作流级别的 permissions: contents: read;PR 并发任务取消; 每个操作都固定 SHA 并附带版本注释;矩阵版本加引号; uv sync --locked。
  • 在 main 上设置分支规则集(或经典保护),仅要求 all-checks-passed;包括管理员在内;启用过期评审批准的忽略。
  • 最终代码树中的 scripts/check_workflows.py 和 zizmor 均以退出码 0 结束。
  • 一个已运行完整检查门禁且通过的 PR——并在 PR 描述中说明门禁的约束内容,以及重命名任务时必须在同一个 PR 中更新分支设置。

失败模式与注意事项

症状 / 风险原因与修复方法
PR 永远处于“等待报告状态”必需检查关联到了已重命名或移除的任务或矩阵分支。应以汇总任务作为门禁;在同一个 PR 中更新设置和任务名称。
汇总任务显示成功,但任务失败缺少 if: always()——任务被跳过了,而被跳过的必需检查会被视为成功。显式结果检查至关重要。
PR 检查已通过,但合并队列卡住工作流缺少 merge_group 触发器,因此必需检查在进入队列时不会报告结果。
矩阵运行的是 Python 3.1未加引号的 3.10 被解析成浮点数。所有版本都要加引号。
一个失败导致所有矩阵分支取消fail-fast 默认为 true;调试时或需要完整的跨版本结果时,将其设为 false。
覆盖率在一个分支失败,但整体没问题每个分支都单独执行阈值检查。应先合并,再在合并任务中统一检查一次。
设置中无法选择必需检查该检查从未运行过。先触发一次工作流(推送 / PR),然后再选择它。
所有检查都通过了,但仍无法合并不同工作流文件中的任务名称重复,导致检查上下文不明确——确保整个仓库中的任务名称唯一。
Agent 的 PR 合并后 CI 通过,但门禁变宽松了检查工作流差异中的 || true、已删除的步骤、放宽的触发条件和已修改的阈值。服务端必需检查 + 管理员强制执行,是本地 Agent 无法自行绕过的唯一防护层;在 .github/ 上设置 CODEOWNERS(供应链技能)可增加审查环节。
Bot 推送或打标签后,工作流未触发使用默认 GITHUB_TOKEN 创建的事件不会启动新的工作流运行——要串联工作流,需要 GitHub App token 或 PAT(属于 python-release 范畴)。
Ruff 发布后,格式检查立即失败工具版本必须来自锁文件(uv sync --locked),确保 CI 和本地运行使用完全相同的版本——切勿在工作流步骤中不固定版本地执行 pip install ruff。
Fork PR 导致缓存投毒pull_request_target + 缓存写入是一条权限提升途径。从 2026-06-26 起,不受信任的触发事件(pull_request_target、issue_comment、来自 fork 的 workflow_run)在默认分支作用域内会获得只读缓存 token——应将其视为最低保障,同时仍要按 fork 可写入缓存具有恶意的情况来设计。

随附资源

  • references/ci-workflow-template.md — 完整的注释版 ci.yml(矩阵、覆盖率合并、汇总任务),以及 变体:通过 needs: 串联的任务、按路径筛选的汇总任务、动态矩阵。
  • references/required-checks-and-rulesets.md — 汇总任务模式选项、规则集与经典保护、gh api 示例、 合并队列、标签规则集、合并受阻诊断手册。
  • references/workflow-hardening.md — SHA 固定(及 Dependabot 警报的权衡)、GITHUB_TOKEN 作用域、zizmor 和 actionlint 的用法、pull_request_target 与缓存信任边界。
  • scripts/check_workflows.py — 用于 .github/workflows/ 的只读卫生扫描器; 发现问题时以非零状态退出,每个问题输出一行机器可读信息。在步骤 7 中运行,并在每次修改工作流后再次运行。

评论

支持 Markdown