关于 python-ci
为 Python 仓库配置并加固 GitHub Actions 质量门禁的技能,供维护仓库 CI 的开发者使用。它涵盖版本矩阵检查、统一汇总状态、分支规则和工作流安全验证。
主要功能
- 01配置 lint、类型检查与测试
- 02按 Python 版本构建矩阵
- 03汇总稳定的必需检查
- 04接入覆盖率合并检查
- 05加固 Actions 与令牌权限
- 06验证工作流和分支规则
适用场景
- 01为 Python 仓库建立 CI 门禁
- 02将测试矩阵接入合并队列
- 03防止跳过任务导致检查误通过
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paldom/ python-skills --skill python-ci安装后在 Claude Code 中输入/python-ci确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/ci-workflow-template.mdrequired-checks-and-rulesets.mdworkflow-hardening.mdscripts/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 中运行,并在每次修改工作流后再次运行。