Repository navigation
feat: require Python 3.10 and automate version reviews - #3537
Conversation
There was a problem hiding this comment.
Pull request overview
This PR raises the SDK’s minimum supported Python runtime to 3.10 and adds policy + automation to keep Python-version support, metadata, documentation, and CI coverage synchronized over time.
Changes:
- Bump
Requires-Python/classifiers/tooling targets to Python 3.10+ and remove now-redundant Python-version dependency markers. - Add deterministic policy checks and wheel/SDist metadata validation (including verifying Python 3.9 rejects the new wheel).
- Expand CI to test min/current runtimes per PR, run a scheduled full-matrix smoke suite, and add a monthly least-privilege Codex-based version review workflow.
Reviewed changes
Copilot reviewed 17 out of 20 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock |
Updates lock resolution and markers for the new minimum Python (3.10+). |
tests/lib/test_bedrock_auth_conformance.py |
Uses zip(..., strict=True) now that Python 3.10+ is required. |
scripts/utils/validate-python-version-wheel.py |
New script to validate Requires-Python in built artifacts and prove Python 3.9 rejects the wheel. |
scripts/utils/validate-httpx2-wheel.py |
Updates metadata assertions and runtime behaviors now that Python 3.10+ is the floor. |
scripts/utils/validate-bedrock-wheel.py |
Updates Bedrock extra validation for the unified supported Botocore requirement. |
scripts/check-python-version-policy.py |
New deterministic check ensuring docs/metadata/CI/tooling agree on Python support policy. |
requirements.lock |
Regenerated Rye lock output for the updated dependency surface. |
requirements-dev.lock |
Regenerated dev lock output for the updated dependency surface. |
README.md |
Updates stated minimum Python version and documents SemVer treatment for floor raises. |
PYTHON_VERSION_POLICY.md |
New policy doc describing support windows, testing expectations, and release treatment. |
pyproject.toml |
Raises requires-python, removes 3.9 classifier, and removes redundant Python markers on extras. |
examples/realtime/push_to_talk_app.py |
Updates inline script metadata to require Python 3.10+. |
CONTRIBUTING.md |
Links to the new Python version policy and documents floor-change requirements. |
AGENTS.md |
Adds repository guidance and a consolidated checklist for Python floor changes. |
.python-version |
Pins the contributor toolchain to Python 3.10.x. |
.github/workflows/python-version-review.yml |
New monthly workflow that snapshots lifecycle/usage data and runs a least-privilege Codex assessment. |
.github/workflows/ci.yml |
Adds per-PR min/current testing, scheduled compatibility smoke matrix, and metadata/rejection validations. |
.github/codex/python-version-review-config.toml |
New least-privilege Codex permission profile for the scheduled review. |
.github/codex/prompts/python-version-review.md |
New Codex prompt defining policy criteria and required output format. |
.github/CODEOWNERS |
Requires SDK team review for Python floor/policy and related automation files. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59fe6ea218
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Can we remove workflow_dispatch from the Codex workflow and make it cron-only? Scheduled workflows already run exclusively on the default branch (main). Otherwise LGMT |
|
@apcha-oai Fixed in 6922777: I removed |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69227778fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06fe88d81d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8406132629
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41ac9de0ce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
HAYDEN-OAI
left a comment
There was a problem hiding this comment.
Reviewed 26090d3 with a thermonuclear maintainability pass. I inspected the complete diff, repository policy and packaging surfaces, monthly automation trust boundary, prior review threads, and current CI. The latest head closes the lockfile synchronization gap; I found no remaining substantive correctness, security, compatibility, or structural-maintainability issues. CI is green for lint, build plus wheel/sdist metadata and Python 3.9 rejection, Bedrock/HTTPX2 validators, Python 3.10/3.14 tests, HTTPX2, examples, CodeQL, breaking-change detection, and Agents SDK integration.
Automated Release PR --- ## 2.49.0 (2026-07-27) Full Changelog: [v2.48.0...v2.49.0](v2.48.0...v2.49.0) ### Features * require Python 3.10 and automate version reviews ([#3537](#3537)) ([d4c151d](d4c151d)) --- This pull request is managed by Stainless's [GitHub App](https://github.com/apps/stainless-app). The [semver version number](https://semver.org/#semantic-versioning-specification-semver) is based on included [commit messages](https://www.conventionalcommits.org/en/v1.0.0/). Alternatively, you can manually set the version number in the title of this pull request. For a better experience, it is recommended to use either rebase-merge or squash-merge when merging this pull request. 🔗 Stainless [website](https://www.stainlessapi.com) 📚 Read the [docs](https://app.stainlessapi.com/docs) 🙋 [Reach out](mailto:support@stainlessapi.com) for help or questions Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
… (OPT89 to OPT91), and OPT21 stops flagging deliberate gate jobs (#140) * ci-speedup: three catalog patterns for slow builds and slow PR checks (OPT89 to OPT91) Adds three catalog-only entries, each quoting changes OpenAI engineers made in their public repos (every figure re-read from the PR body): - OPT89 The Slowest Platform Compiles Its Own Tests (data-driven, risk MEDIUM): build once on a fast runner, run natively per platform. openai/codex#20585, openai/codex#23358, openai/tiktoken 853da97. - OPT90 Every Pull Request Pays for Coverage That Could Run After Merge (data-driven, risk MEDIUM). openai/codex#16072, openai/openai-python#3537 (no timings), openai/codex#20585 (its saving comes from two changes the PR does not separate, so none is credited to this pattern). - OPT91 The Compile Is the Long Pole and Nobody Profiled It (data-driven, risk MEDIUM). openai/codex#23710 (ThinLTO), openai/codex#16630 (async_trait removal; no CI run linked). All three are detector: manual, like OPT25 and OPT43, so they are never emitted. Counts 86 -> 89 (78 hygiene + 11 structural) in SKILL.md, ARCHITECTURE.md and evals.json; SKILL.md line count unchanged (515). Red-first exemption: catalog and doc text only, no behavior to regress. Scoped run: 249 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT89 says how to tell build from test time when one step does both Checked against openai/codex's real runs: on both cited PRs the slow leg compiles and tests inside one step (bazel test, cargo nextest), so step timings alone cannot confirm detection step 2. The step now names the evidence that can (run log, a build-timing artifact, or a test-only run), and the #20585 example says its baseline job is named "Local Bazel build". Text only, red-first exempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT21 no longer flags a wait on a cheap check job OPT21 flagged any job whose `needs:` read no outputs and downloaded no artifact, and each hit was credited a flat 30s wall-clock saving. That fired on deliberate cheap gates, where a test job waits on a lint, pre-commit or lockfile-check job so the expensive jobs never start when the cheap check fails (openai/whisper whisper-test needing pre-commit; openai/openai-python lint, build, test and five more needing dependency-locks). The detector now skips a job when every job it needs is a check-only gate: every step is recognised setup (checkout, setup-*, cache, dependency install) or a recognised check (lint, format check, pre-commit, type check, lockfile check, actionlint and similar, or a step named Check/Verify/Validate/Lint/...), at least one step is a check, nothing builds, tests, deploys or publishes, and the job has no outputs, services, container, environment, reusable uses: or continue-on-error. A waiting job with always()/failure()/cancelled() in its if: is still flagged. Anything unclassifiable fails closed to the old behaviour. The OPT21 catalog entry gains a "When a needs: edge is the right call" paragraph. Red run (new tests against the unfixed detector): E AssertionError: assert ['whisper-test'] == [] E AssertionError: assert ['build', 'lint', 'test'] == [] E AssertionError: assert ['e2e'] == [] FAILED test_scan_detectors.py::test_opt21_not_flagged_on_a_pre_commit_gate FAILED test_scan_detectors.py::test_opt21_not_flagged_on_a_lockfile_check_gate FAILED test_scan_detectors.py::test_opt21_not_flagged_on_a_lint_gate 3 failed, 11 passed, 208 deselected Green: 14 passed (OPT21 tests); 283 passed across test_scan_detectors, test_offline_pipeline_e2e, test_evidence_claim_guards and test_catalog_anchor_aliases. Real files: whisper test.yml OPT21 hits 1 -> 0; openai-python ci.yml OPT21 hits 8 -> 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: changelog entry for the OPT21 cheap-gate fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT90 and OPT91 detection steps match what OpenAI's real repos show Checked each entry's detection steps against openai/codex and openai/openai-python workflows and run history: - OPT90: step 2 now says how to count leg-unique failures (codex's Windows test legs often failed when every other leg passed, which is the case the step keeps on the PR path). A PR path already running a subset is the recommended shape, not an instance. The subset can be empty for a check another PR workflow covers (codex #16072 kept no compile or test legs). openai-python #3537 is described as adopting the shape, not as a measured saving: its full matrix was new. - OPT91: a clean-by-design release build satisfies step 2, and a timing report CI produces but nobody reads counts as unread (codex already ran cargo --timings in release CI). The #23710 example says only codex's Windows release builds still used fat LTO. Codex's stable Windows release builds fell from 64.8 to 72.4 minutes (v0.131 to v0.137) to 43.2 and 48.8 minutes (v0.138, v0.139), in line with the PR's figure. Text only, red-first exempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT90's guardrail fits a scheduled run, and OPT91 profiles with the slow job's cache state OPT90's guardrail required the moved run to start on every merge, which a scheduled run cannot do although the fix recipe offers one. It now asks that every merged commit is covered and that a release never ships a commit the full matrix has not passed. OPT91's first fix step profiled a clean build, which adds back dependency compilation the slow job does not repeat. It now profiles with the slow job's own cache state and keeps a clean build as a comparison only. Text-only catalog edits, so no red-first test (doc-only exemption). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT21's gate check no longer lets a check-style step name vouch for a test run, and no longer counts gates that cannot fail Review of the OPT21 cheap-gate carve-out found it silencing real findings: a step named "Verify" or "Check" counted as a check whatever it ran unless a short denylist caught it, so `mvn -B verify`, `make check`, `pnpm -r test`, `nix flake check`, `bundle exec rspec`, `ctest`, `python -m unittest` and `./ci/run_all_tests.sh` under such names all made a gate. A check that cannot fail (`|| true`, `|| exit 0`), a check that runs only under an `if:`, a gate with steps the walker could not read, and `npm run lint-and-build` (read as `lint`) also made a gate. - The name rule now fails closed: a check-named step counts only when every line is a recognised check or setup line, an inline interpreter script, a shell test, an assignment, a read-only git command, or `git show <file> | python -`. - A check that cannot fail, a conditional check, and an unreadable step never make a gate; npm/make script names must match whole. - Each wait left unflagged is logged at DEBUG. - Doc fixes: the catalog paragraph names the rules, the docstrings match the code, and OPT89's changelog line says "after merge or on a schedule". Red proof: against the unfixed scan.py, 15 new tests fail, e.g. FAILED test_opt21_still_flags_a_test_run_under_a_check_name[Verify-mvn -B verify] FAILED test_opt21_still_flags_a_test_run_under_a_check_name[Check-make check] FAILED test_opt21_still_flags_a_wait_on_a_gate_it_cannot_vouch_for[check-that-cannot-fail] FAILED test_opt21_still_flags_a_wait_on_a_gate_it_cannot_vouch_for[unreadable-step] FAILED test_opt21_gate_suppression_leaves_a_debug_trace 15 failed, 28 passed With the fix: 43 passed. 14 further controls pin the job-level disqualifiers, result conditions, two-gate needs, and missing or empty upstream jobs. The real openai/openai-python ci.yml and openai/whisper test.yml still give no OPT21 finding (8 and 1 waits left unflagged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT21's gate check closes the remaining silent drops and no longer backtracks for minutes on a long flag run On top of the check-name tightening, a gate still silenced real waits when: - a lint was piped or chained into other work (`eslint . | ./heavy.sh`, `pre-commit run || ./run-integration-tests.sh`) or used `--exit-zero`; - a script only started with lint (`./scripts/lint-and-test.sh`); - a step used a local composite action (`./setup-and-test`), a custom `shell:`, or the job set `defaults:`; - the job had a `wait:`/`cancel:` control step the walker drops; - the gate itself waited on a build (`e2e` needs `lint` needs `build`). All now keep the finding. The runner-prefix flag pattern in the check-line and inline-script regexes was ambiguous (`--f` read as `-` + `-f`), so a 16-flag `uv run` line took 28s (55s under a check name) to classify; flags now start with one or two dashes then a word character, and a value never starts with a dash, so matching is linear. Red proof against the unfixed scan.py (test_scan_detectors.py -k opt21): FAILED ...work_dressed_as_a_check[lint-piped-into-other-work] FAILED ...work_dressed_as_a_check[lint-or-other-work] FAILED ...work_dressed_as_a_check[lint-and-test-script] FAILED ...work_dressed_as_a_check[lint-with-exit-zero] FAILED ...work_dressed_as_a_check[local-composite-action] FAILED ...work_dressed_as_a_check[custom-shell] FAILED ...gate_whose_steps_are_not_plain[control-step] FAILED ...gate_whose_steps_are_not_plain[job-default-shell] FAILED ...wait_through_a_gate_that_waits_on_a_build FAILED ...stays_fast_on_a_long_flag_run (x2: 27.8s and 55.8s, budget 5s) 11 failed, 58 passed With the fix: 279 passed (detector + anchor tests). openai/whisper test.yml and openai/openai-python ci.yml still scan to 0 OPT21 findings. Text-only corrections (doc-only exemption): OPT89 says the 28m12s and 9m16s runs are not like for like and quotes the full job name; OPT90 names the real second change in codex #20585 and the lint package tests #16072 kept; OPT91 says only stable Windows builds used fat LTO and quotes #16630's futures exactly; the three entries cite OPT86 and OPT88 (not OPT25 and OPT43, which are emitted) as unemitted peers. The OPT21 paragraph and the changelog name the added rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT86 and OPT88 stop naming OPT25 and OPT43 as patterns nothing detects Both entries said "Like other `detector: manual` entries (for example OPT25 and OPT43), no pass emits ...", but collect_runs emits OPT25 and OPT43 findings. They now name each other, matching the OPT89 to OPT91 wording. Headings and anchors unchanged. Text-only catalog and changelog edit, so no red-first test (doc-only exemption). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci-speedup: OPT21's gate paragraph reads as a short lead and four bullets The "When a `needs:` edge is the right call" paragraph was one long sentence of nested parentheses. It is now a lead sentence (the gate's own duration in wall clock traded for runner minutes saved when it fails) and bullets for what counts as a check-only gate, what disqualifies one, when the wait is still flagged, and that cheap is judged by what the gate runs, not how long it takes. Same facts as before; the OPT21 heading and anchor are unchanged. Text-only catalog edit, so no red-first test (doc-only exemption). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Why
Python 3.9 reached upstream end of life, while Python 3.10 remains supported through October 2026. Internal usage analysis estimates Python 3.9 at 2.09% of identifiable SDK downloads over 30 days and 2.44% over 90 days. The support policy, decision rationale, and rollout analysis are documented in the Python SDK version support PRD.
The automation follows the existing openai-go design: lifecycle and public usage inputs are snapshotted before Codex runs; the pinned agent runs as an unprivileged user with no command network access or repository writes; and a separate job without the OpenAI credential may publish an advisory issue. It never changes
Requires-Pythonautomatically.User impact
Python 3.9 users remain on
openai==2.48.0, the final compatible release. Installing the next minor release on Python 3.9 is rejected by standard package metadata rather than producing an import-time failure. Python 3.10 through 3.14 remain supported, with Python 3.15 tested as allowed-failure prerelease coverage.A runtime-floor increase is treated as an SDK minor release rather than a patch or major release when documented APIs remain compatible on supported runtimes and
Requires-Pythonprotects unsupported installers.Validation
rye build./scripts/lint./scripts/test: 7,079 passed, 29 skippedRequires-PythonvalidationRelease note
The OpenAI Python SDK now requires Python 3.10 or later.
openai==2.48.0is the final release installable on Python 3.9. Previously published versions remain available, but unsupported runtimes and older SDK releases do not receive guaranteed fixes or security backports.