Run only the pydantic-clai2 CI cells for pull requests confined to it, and drop the line-by-line review checkbox (#8960)

This commit is contained in:
Mike Pfaffenberger
2026-09-28 15:45:13 -04:00
committed by GitHub
parent 10c5f9b0bd
commit af3ece3d80
4 changed files with 63 additions and 15 deletions
-1
View File
@@ -22,7 +22,6 @@
### Checklist
- [ ] Any **AI generated code** has been reviewed line-by-line by the human PR author, who stands by it.
- [ ] No **breaking changes** in accordance with the [version policy](https://github.com/pydantic/pydantic-ai/blob/main/docs/version-policy.md).
- [ ] Any permitted **compatibility impact** has a label, warning, migration, release note, and exact API-check waiver.
- [ ] **PR title** is fit for the [release changelog](https://github.com/pydantic/pydantic-ai/releases).
+60 -13
View File
@@ -43,6 +43,7 @@ jobs:
pull-requests: read
outputs:
docs_only: ${{ steps.classify.outputs.docs_only }}
clai2_only: ${{ steps.classify.outputs.clai2_only }}
pyright_changed: ${{ steps.classify.outputs.pyright_changed }}
steps:
- name: Classify pull request changes
@@ -53,6 +54,7 @@ jobs:
run: |
run_all_checks() {
echo 'docs_only=false' >> "$GITHUB_OUTPUT"
echo 'clai2_only=false' >> "$GITHUB_OUTPUT"
echo 'pyright_changed=true' >> "$GITHUB_OUTPUT"
}
@@ -90,6 +92,17 @@ jobs:
esac
}
# Nothing outside `pydantic-clai2` imports it, so a change confined to these paths cannot
# break another package's suite: it runs only the `pydantic-clai2` test cells, and skips
# the jobs that exercise the rest of the workspace. Shared inputs (`pyproject.toml`,
# `uv.lock`, `tests/conftest.py`, ...) are outside them and still run everything.
is_clai2() {
case "$1" in
src/pydantic_clai2/*|tests/clai2/*) ;;
*) return 1 ;;
esac
}
is_pyright_input() {
# All Python/stub files conservatively cover Pyright's include and import graph;
# the remaining paths change its command, configuration, dependencies, or this gate.
@@ -100,6 +113,7 @@ jobs:
}
docs_only=true
clai2_only=true
pyright_changed=false
for changed_file in "${changed_files[@]}"; do
IFS=$'\t' read -r filename previous_filename <<< "$changed_file"
@@ -107,12 +121,17 @@ jobs:
{ [ -n "$previous_filename" ] && ! is_docs_markdown "$previous_filename"; }; then
docs_only=false
fi
if ! is_clai2 "$filename" || \
{ [ -n "$previous_filename" ] && ! is_clai2 "$previous_filename"; }; then
clai2_only=false
fi
if is_pyright_input "$filename" || \
{ [ -n "$previous_filename" ] && is_pyright_input "$previous_filename"; }; then
pyright_changed=true
fi
done
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
echo "clai2_only=$clai2_only" >> "$GITHUB_OUTPUT"
echo "pyright_changed=$pyright_changed" >> "$GITHUB_OUTPUT"
# The `pydantic-ai-harness` sandbox live tests start real, billed sandboxes, one provider at
@@ -331,7 +350,7 @@ jobs:
# mypy and quality checks are a bit slower than other jobs, so we run them separately
mypy:
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -443,12 +462,25 @@ jobs:
paths: tests/clai2
- name: standard
command: ""
# A single-value dimension, so the `exclude` below can drop every cell but
# `pydantic-clai2` when `classify` found a change confined to it.
clai2_only: ["${{ needs.classify.outputs.clai2_only }}"]
exclude:
- clai2_only: "true"
install: { name: pydantic-ai-slim }
- clai2_only: "true"
install: { name: pydantic-evals }
- clai2_only: "true"
install: { name: pydantic-ai-harness }
- clai2_only: "true"
install: { name: standard }
env:
CI: true
# Coverage adds roughly 50% to test execution before Python 3.14. Collect every install shape
# on 3.14, where coverage uses the faster sys.monitoring core; the other versions still run
# every applicable test without duplicate coverage.
COLLECT_COVERAGE: ${{ matrix.python-version == '3.14' }}
# every applicable test without duplicate coverage. A `pydantic-clai2`-only change also
# collects it on 3.10: `tests/clai2` has 3.10-only branches that `all-extras` covers otherwise.
COLLECT_COVERAGE: ${{ matrix.python-version == '3.14' || matrix.clai2_only == 'true' && matrix.python-version == '3.10' }}
BLOCKBUSTER_ENABLED: ${{ matrix.python-version == '3.13' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -513,7 +545,7 @@ jobs:
test-all-extras:
name: test on ${{ matrix.python-version }} (all-extras, shard ${{ matrix.shard }}/2)
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
@@ -652,7 +684,7 @@ jobs:
test-durable-exec:
name: test durable-exec on ${{ matrix.python-version }} (${{ matrix.resolution }})
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
@@ -748,7 +780,7 @@ jobs:
test-lowest-versions:
name: test on ${{ matrix.python-version }} (lowest-versions, shard ${{ matrix.shard }}/2)
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
@@ -856,7 +888,7 @@ jobs:
test-temporal-latest:
name: test Temporal latest on Python 3.10
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
@@ -882,7 +914,7 @@ jobs:
test-examples:
name: test examples on ${{ matrix.python-version }}
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
@@ -911,7 +943,7 @@ jobs:
test-harness-browser-use:
name: test harness browser-use
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
@@ -940,7 +972,7 @@ jobs:
test-fastmcp-4:
name: test FastMCP 4 compatibility
needs: classify
if: needs.classify.outputs.docs_only != 'true'
if: needs.classify.outputs.docs_only != 'true' && needs.classify.outputs.clai2_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
@@ -1189,7 +1221,9 @@ jobs:
coverage:
runs-on: ubuntu-latest
needs: [classify, test, test-all-extras, test-durable-exec, test-lowest-versions, test-fastmcp-4]
if: needs.classify.outputs.docs_only != 'true'
# A `pydantic-clai2`-only change skips every test job but `test`, and a skipped need would
# otherwise skip this job too.
if: ${{ !failure() && !cancelled() && needs.classify.outputs.docs_only != 'true' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
@@ -1209,7 +1243,17 @@ jobs:
- run: uv sync --group dev --group coverage
- run: uv run --no-sync coverage combine
- run: uv run --no-sync coverage report
# Only the `pydantic-clai2` cells ran for a change confined to it, so they can only vouch
# for its own tests: the rest of the workspace is covered on the full path.
- name: coverage report
env:
CLAI2_ONLY: ${{ needs.classify.outputs.clai2_only }}
run: |
if [ "$CLAI2_ONLY" = true ]; then
uv run --no-sync coverage report --include='tests/clai2/*'
else
uv run --no-sync coverage report
fi
- run: uv run --no-sync strict-no-cover
env:
@@ -1248,7 +1292,8 @@ jobs:
with:
# The canary is advisory because it tests dependency versions we do not lock; its failures
# signal upstream drift rather than release blockers. It runs on tag refs only, and
# `docs-only` runs only for Markdown-only PRs.
# `docs-only` runs only for Markdown-only PRs, and a PR confined to `pydantic-clai2` skips the
# jobs that exercise the rest of the workspace.
# On tags only `docs-only` and `sandbox-live-changes` may skip; full CI jobs may skip only
# on the reduced PR path. The harness integration jobs run only when their paths change (always
# on tags), and LocalStack also skips where its secret is unreadable. `sandbox-live` may skip off
@@ -1260,6 +1305,8 @@ jobs:
${{
needs.classify.outputs.docs_only == 'true'
&& 'quality,mypy,test,test-all-extras,test-durable-exec,test-lowest-versions,test-temporal-latest,test-examples,test-fastmcp-4,test-harness-browser-use,harness-integration-changes,harness-localstack-integration,harness-mongodb-integration,harness-redis-integration,latest-versions-canary,coverage,sandbox-live'
|| needs.classify.outputs.clai2_only == 'true'
&& 'docs-only,mypy,test-all-extras,test-durable-exec,test-lowest-versions,test-temporal-latest,test-examples,test-fastmcp-4,test-harness-browser-use,latest-versions-canary,harness-integration-changes,harness-localstack-integration,harness-mongodb-integration,harness-redis-integration,sandbox-live'
|| github.ref_type == 'tag'
&& 'docs-only,harness-integration-changes,harness-localstack-integration,sandbox-live-changes'
|| 'docs-only,latest-versions-canary,harness-integration-changes,harness-localstack-integration,harness-mongodb-integration,harness-redis-integration,sandbox-live'
+2
View File
@@ -65,6 +65,8 @@ repos:
args: [run, pytest, "clai/update_readme.py"]
language: system
types_or: [python, markdown]
# `clai` never imports `pydantic-clai2`, so a commit confined to it cannot change the output.
exclude: ^(src/pydantic_clai2|tests/clai2)/
pass_filenames: false
- id: format
name: Format
+1 -1
View File
@@ -68,7 +68,7 @@ All changes need to:
- update/add all relevant documentation, following the existing voice and patterns
- update the relevant agent skills when introducing a new feature or when a skill needs to reflect the correct mechanics; Pydantic AI skills belong in [pydantic_ai_slim/pydantic_ai/.agents/skills/building-pydantic-ai-agents/](pydantic_ai_slim/pydantic_ai/.agents/skills/building-pydantic-ai-agents/), Pydantic AI Harness skills in [src/pydantic_ai_harness/pydantic_ai_harness/.agents/skills/pydantic-ai-harness/](src/pydantic_ai_harness/pydantic_ai_harness/.agents/skills/pydantic-ai-harness/), while repository workflow skills live under [.claude/skills/](.claude/skills/)
When you submit a PR, make sure you include the [PR template](.github/pull_request_template.md) and fill in the issue number that should be closed when the PR is merged. The "AI generated code" checkbox should always be checked manually by the user in the UI, not by the agent.
When you submit a PR, make sure you include the [PR template](.github/pull_request_template.md) and fill in the issue number that should be closed when the PR is merged.
PR titles feed directly into the release changelog. Write one as an imperative sentence naming the change — no `fix:` / `docs:` / `chore:` prefix, which belongs on the commit subject and not on the title — and wrap every code identifier (class names, keyword arguments, module paths, CLI flags, env vars, file paths) in backticks. Check the convention against merged PRs rather than commit subjects, which follow a different one: `gh pr list --state merged --limit 20`.