Skip to content

Commit cce5ab6

Browse files
authored
ci(checks): move static checks out of sharded nox (#257)
Run pylint and test_types together in a dedicated Ubuntu Python matrix job instead of repeating them inside every sharded nox job. This should reduce duplicated work across OSes and shards, improving CI wall time and total runner usage while preserving multi-version static-check coverage. Add nox-matrix session exclusions so shard reproduction matches CI, and update the SDK agent docs and CI triage skill to reflect the new static_checks workflow path.
1 parent 2aee70f commit cce5ab6

6 files changed

Lines changed: 87 additions & 27 deletions

File tree

.agents/skills/sdk-ci-triage/SKILL.md

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,32 @@ Read when relevant:
3434

3535
- `lint`: pre-commit diff-based checks
3636
- `ensure-pinned-actions`: workflow hygiene
37+
- `static_checks`: Ubuntu-only Python matrix for `pylint` and `test_types`
3738
- `smoke`: install/import matrix across Python and OS
3839
- `nox`: provider and core test matrix, sharded through `py/scripts/nox-matrix.py`
3940
- `adk-py`: reusable workflow for ADK coverage
4041
- `langchain-py`: reusable workflow for LangChain coverage
4142
- `upload-wheel`: build wheel sanity check
4243

43-
The most common failure source is the `nox` matrix job.
44+
The most common failure source is still the `nox` matrix job, but `pylint` and `test_types` failures now surface through `static_checks`, not through `nox`.
4445

4546
## Standard Workflow
4647

4748
1. Identify the failing PR, run, or job.
4849
2. Inspect the failing job logs with `gh`.
4950
3. Determine which workflow branch failed:
5051
- `lint`
52+
- `static_checks`
5153
- `smoke`
5254
- `nox`
5355
- reusable workflow (`adk-py`, `langchain-py`)
5456
- `upload-wheel`
5557
4. For `nox` failures, map the matrix job to the exact nox session and pinned provider version from the logs.
56-
5. Reproduce the narrowest failing command locally.
57-
6. Fix the bug.
58-
7. Re-run the narrowest failing command first.
59-
8. Expand only if shared code changed.
58+
5. For `static_checks` failures, identify whether `pylint` or `test_types` failed under the reported Python version.
59+
6. Reproduce the narrowest failing command locally.
60+
7. Fix the bug.
61+
8. Re-run the narrowest failing command first.
62+
9. Expand only if shared code changed.
6063

6164
Do not start by running the whole suite locally unless the failure genuinely spans many sessions.
6265

@@ -94,25 +97,29 @@ gh api repos/braintrustdata/braintrust-sdk-python/actions/jobs/<job-id>/logs
9497
Job names look like this:
9598

9699
```text
97-
nox (3.10, ubuntu-latest, 0)
100+
nox (3.10, ubuntu-24.04, 0)
98101
```
99102

100103
That means:
101104

102105
- Python `3.10`
103-
- OS `ubuntu-latest`
106+
- OS `ubuntu-24.04`
104107
- shard `0` out of 4
105108

106109
The workflow runs:
107110

108111
```bash
109-
mise exec python@<python-version> -- python ./py/scripts/nox-matrix.py <shard> 4
112+
mise exec python@<python-version> -- python ./py/scripts/nox-matrix.py <shard> 4 \
113+
--exclude-session pylint \
114+
--exclude-session test_types
110115
```
111116

112117
Use a dry run first to see which sessions belong to the shard:
113118

114119
```bash
115-
mise exec python@3.10 -- python ./py/scripts/nox-matrix.py 0 4 --dry-run
120+
mise exec python@3.10 -- python ./py/scripts/nox-matrix.py 0 4 --dry-run \
121+
--exclude-session pylint \
122+
--exclude-session test_types
116123
```
117124

118125
Then inspect the failing logs to find the exact session name, for example:
@@ -161,6 +168,23 @@ make lint
161168
make pylint
162169
```
163170

171+
### `static_checks`
172+
173+
The `static_checks` job is an Ubuntu-only Python matrix that runs `pylint` and `test_types` together for each configured Python version.
174+
175+
Local equivalents:
176+
177+
```bash
178+
mise exec python@3.10 -- nox -f ./py/noxfile.py -s pylint test_types
179+
```
180+
181+
If only one of the two sessions failed in CI, narrow locally to that specific session:
182+
183+
```bash
184+
mise exec python@3.10 -- nox -f ./py/noxfile.py -s pylint
185+
mise exec python@3.10 -- nox -f ./py/noxfile.py -s test_types
186+
```
187+
164188
### `smoke`
165189

166190
The smoke job validates install + import across OS and Python versions.
@@ -276,7 +300,9 @@ Preferred progression:
276300

277301
```bash
278302
# 1. Inspect the failing shard
279-
mise exec python@3.10 -- python ./py/scripts/nox-matrix.py 0 4 --dry-run
303+
mise exec python@3.10 -- python ./py/scripts/nox-matrix.py 0 4 --dry-run \
304+
--exclude-session pylint \
305+
--exclude-session test_types
280306

281307
# 2. Reproduce the exact session
282308
cd py
@@ -299,7 +325,7 @@ When answering a CI-triage question, report:
299325
Good example structure:
300326

301327
```text
302-
The failing job is `nox (3.10, ubuntu-latest, 0)`.
328+
The failing job is `nox (3.10, ubuntu-24.04, 0)`.
303329
Within that shard, the failing session is `test_google_genai(1.30.0)`.
304330
The root cause is that the tests import a symbol that does not exist in google-genai 1.30.0, even though it exists in newer versions.
305331
You can reproduce it locally with `cd py && nox -s "test_google_genai(1.30.0)"`.
@@ -311,6 +337,7 @@ The fix is to gate the behavior for older versions or stop assuming the newer AP
311337
Avoid these common mistakes:
312338

313339
- guessing the session from the provider name without checking `py/noxfile.py`
340+
- forgetting that CI excludes `pylint` and `test_types` from the sharded `nox` job
314341
- reproducing with `latest` when CI failed on an older pinned version
315342
- running from repo root when the real SDK command belongs in `py/`
316343
- fixing the symptom in tests without understanding the provider-version contract

.github/workflows/adk-py-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
jobs:
1111
test:
12-
runs-on: ubuntu-latest
12+
runs-on: ubuntu-24.04
1313
timeout-minutes: 15
1414

1515
steps:

.github/workflows/checks.yaml

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ permissions:
1010

1111
jobs:
1212
lint:
13-
runs-on: ubuntu-latest
13+
runs-on: ubuntu-24.04
1414
timeout-minutes: 10
1515
steps:
1616
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
@@ -26,13 +26,31 @@ jobs:
2626
mise exec -- pre-commit run --from-ref origin/${{ github.base_ref || 'main' }} --to-ref HEAD
2727
2828
ensure-pinned-actions:
29-
runs-on: ubuntu-latest
29+
runs-on: ubuntu-24.04
3030
timeout-minutes: 5
3131
steps:
3232
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
3333
- name: Ensure SHA pinned actions
3434
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@70c4af2ed5282c51ba40566d026d6647852ffa3e # v5.0.1
3535

36+
static_checks:
37+
runs-on: ubuntu-24.04
38+
timeout-minutes: 20
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
43+
steps:
44+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
45+
- name: Setup Python environment
46+
uses: ./.github/actions/setup-python-env
47+
with:
48+
python-version: ${{ matrix.python-version }}
49+
- name: Run pylint and type tests
50+
shell: bash
51+
run: |
52+
mise exec python@${{ matrix.python-version }} -- nox -f ./py/noxfile.py -s pylint test_types
53+
3654
smoke:
3755
runs-on: ${{ matrix.os }}
3856
timeout-minutes: 20
@@ -41,7 +59,7 @@ jobs:
4159
fail-fast: false
4260
matrix:
4361
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
44-
os: [ubuntu-latest, windows-latest]
62+
os: [ubuntu-24.04, windows-2025]
4563

4664
steps:
4765
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
@@ -66,7 +84,7 @@ jobs:
6684
fail-fast: false
6785
matrix:
6886
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
69-
os: [ubuntu-latest, windows-latest]
87+
os: [ubuntu-24.04, windows-2025]
7088
shard: [0, 1, 2, 3]
7189

7290
steps:
@@ -78,7 +96,9 @@ jobs:
7896
- name: Run nox tests (shard ${{ matrix.shard }}/4)
7997
shell: bash
8098
run: |
81-
mise exec python@${{ matrix.python-version }} -- python ./py/scripts/nox-matrix.py ${{ matrix.shard }} 4
99+
mise exec python@${{ matrix.python-version }} -- python ./py/scripts/nox-matrix.py ${{ matrix.shard }} 4 \
100+
--exclude-session pylint \
101+
--exclude-session test_types
82102
83103
adk-py:
84104
uses: ./.github/workflows/adk-py-test.yaml
@@ -90,7 +110,7 @@ jobs:
90110
needs:
91111
- smoke
92112
- nox
93-
runs-on: ubuntu-latest
113+
runs-on: ubuntu-24.04
94114
timeout-minutes: 10
95115
steps:
96116
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
@@ -114,12 +134,13 @@ jobs:
114134
needs:
115135
- lint
116136
- ensure-pinned-actions
137+
- static_checks
117138
- smoke
118139
- nox
119140
- adk-py
120141
- langchain-py
121142
- upload-wheel
122-
runs-on: ubuntu-latest
143+
runs-on: ubuntu-24.04
123144
timeout-minutes: 5
124145
if: always()
125146
steps:
@@ -138,12 +159,13 @@ jobs:
138159
}
139160
140161
check_result "lint" "${{ needs.lint.result }}"
141-
check_result "ensure-pinned-actions" "${{ needs.ensure-pinned-actions.result }}"
162+
check_result "ensure-pinned-actions" "${{ needs['ensure-pinned-actions'].result }}"
163+
check_result "static_checks" "${{ needs.static_checks.result }}"
142164
check_result "smoke" "${{ needs.smoke.result }}"
143165
check_result "nox" "${{ needs.nox.result }}"
144-
check_result "adk-py" "${{ needs.adk-py.result }}"
145-
check_result "langchain-py" "${{ needs.langchain-py.result }}"
146-
check_result "upload-wheel" "${{ needs.upload-wheel.result }}"
166+
check_result "adk-py" "${{ needs['adk-py'].result }}"
167+
check_result "langchain-py" "${{ needs['langchain-py'].result }}"
168+
check_result "upload-wheel" "${{ needs['upload-wheel'].result }}"
147169
148170
if [ "$FAILED" -ne 0 ]; then
149171
echo "One or more required checks failed"

.github/workflows/langchain-py-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55

66
jobs:
77
test:
8-
runs-on: ubuntu-latest
8+
runs-on: ubuntu-24.04
99
timeout-minutes: 15
1010

1111
steps:

AGENTS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Use this file as the default playbook for work in this repository.
1515
2. **Use `mise` as the source of truth for tools and environment.**
1616

1717
3. **Do not guess test commands or version coverage.**
18-
- `py/noxfile.py` is the source of truth for nox session names, provider/version matrices, and CI coverage.
18+
- `py/noxfile.py` is the source of truth for nox session names, provider/version matrices, and local reproduction commands.
19+
- `.github/workflows/checks.yaml` is the source of truth for which sessions run in CI, on which Python versions, and outside vs. inside the nox shard matrix.
1920
- For provider and integration work, also check `py/src/braintrust/integrations/versioning.py`.
2021

2122
4. **Keep changes narrow and validate with the smallest relevant test first.**
@@ -116,7 +117,7 @@ Do not guess:
116117
- supported provider versions
117118
- which tests a provider session runs
118119

119-
Check `py/noxfile.py` and reproduce with the exact local session CI uses.
120+
Check `py/noxfile.py` and `.github/workflows/checks.yaml`, then reproduce with the exact local session CI uses.
120121

121122
### Run the smallest relevant test first
122123

@@ -143,6 +144,8 @@ Before changing provider/integration behavior:
143144

144145
- `test_core` runs without optional vendor packages.
145146
- `test_types` runs pyright, mypy, and pytest on `py/src/braintrust/type_tests/`.
147+
- CI runs `pylint` and `test_types` via the dedicated `static_checks` workflow job on Ubuntu across the configured Python matrix, not inside the sharded `nox` job.
148+
- The sharded `nox` workflow excludes `pylint` and `test_types`; use `py/scripts/nox-matrix.py --exclude-session ...` when reproducing shard membership locally.
146149
- wrapper coverage is split across dedicated nox sessions by provider/version.
147150
- `test-wheel` is a wheel sanity check and requires a built wheel first.
148151

py/scripts/nox-matrix.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
by weight descending and greedily assigns each to the lightest shard.
77
88
Usage:
9-
python nox-matrix.py <shard_index> <number_of_shards> [--dry-run]
9+
python nox-matrix.py <shard_index> <number_of_shards> [--dry-run] [--exclude-session <name> ...]
1010
"""
1111

1212
import argparse
@@ -80,6 +80,12 @@ def main() -> None:
8080
parser.add_argument("shard_index", type=int, help="Zero-based shard index")
8181
parser.add_argument("num_shards", type=int, help="Total number of shards")
8282
parser.add_argument("--dry-run", action="store_true", help="Print assignment without running nox")
83+
parser.add_argument(
84+
"--exclude-session",
85+
action="append",
86+
default=[],
87+
help="Exclude a nox session from shard assignment. May be passed multiple times.",
88+
)
8389
parser.add_argument(
8490
"--output-durations",
8591
type=Path,
@@ -108,6 +114,8 @@ def main() -> None:
108114
weights_file = root_dir / "py" / "scripts" / "session-weights.json"
109115

110116
all_sessions = get_nox_sessions(noxfile)
117+
excluded_sessions = set(args.exclude_session)
118+
all_sessions = [session for session in all_sessions if session not in excluded_sessions]
111119
weights, default_weight = load_weights(weights_file)
112120
shard_assignments = assign_shards(all_sessions, args.num_shards, weights, default_weight)
113121

0 commit comments

Comments
 (0)