[codex] Add independent beta release for the Python SDK (#24828)

## Why

`openai-codex` needs a beta release lifecycle without requiring beta
releases of its pinned runtime package. Previously, SDK staging rewrote
its runtime dependency to the SDK version, which made an SDK-only beta
impossible.

## What changed

- Set the initial SDK beta version to `0.1.0b1` and pin it to published
stable `openai-codex-cli-bin==0.132.0`.
- Decoupled SDK release staging from runtime versioning so it preserves
the reviewed exact runtime pin.
- Added a `python-v*` tag workflow that builds and publishes only
`openai-codex` through PyPI trusted publishing.
- Removed the Beta classifier from runtime package metadata for future
runtime publications.
- Regenerated protocol-derived SDK models from the selected stable
runtime package.

`0.132.0` is the newest stable runtime admitted by the checked-in
dependency date fence and retains the Linux wheel family currently used
by SDK CI.

## Release setup

Before pushing `python-v0.1.0b1`, configure PyPI trusted publishing for
the `openai-codex` project with workflow `python-sdk-release.yml`,
environment `pypi`, and job `publish-python-sdk`.

## Validation

- `uv run --frozen --extra dev ruff check src/openai_codex scripts
examples tests`
- Parsed `.github/workflows/python-sdk-release.yml` with PyYAML.
- Built staged release artifacts locally:
`openai_codex-0.1.0b1-py3-none-any.whl` and
`openai_codex-0.1.0b1.tar.gz`.
- Verified wheel metadata pins `openai-codex-cli-bin==0.132.0`.
- Tests are deferred to online CI for this PR.
This commit is contained in:
Ahmed Ibrahim
2026-05-27 17:57:51 -07:00
committed by GitHub
Unverified
parent 304d15cab0
commit 4d0c4cd058
9 changed files with 335 additions and 292 deletions
@@ -158,8 +158,8 @@ def test_schema_normalization_only_flattens_string_literal_oneofs(
"AuthMode",
"InputModality",
"ExperimentalFeatureStage",
"CommandExecOutputStream",
"ProcessOutputStream",
"CommandExecOutputStream",
]
@@ -249,11 +249,11 @@ def test_source_sdk_package_pins_published_runtime() -> None:
"runtime_pin": script.pinned_runtime_version(),
"dependencies": pyproject["project"]["dependencies"],
} == {
"sdk_version": "0.131.0a4",
"runtime_pin": "0.131.0a4",
"sdk_version": "0.1.0b1",
"runtime_pin": "0.132.0",
"dependencies": [
"pydantic>=2.12",
"openai-codex-cli-bin==0.131.0a4",
"openai-codex-cli-bin==0.132.0",
],
}
@@ -284,19 +284,26 @@ def test_release_metadata_retries_without_invalid_auth(
assert authorizations == ["Bearer invalid-token", None]
def test_runtime_setup_uses_pep440_package_version_and_codex_release_tags() -> None:
"""The SDK uses PEP 440 package pins and converts only when fetching releases."""
def test_runtime_setup_reads_independent_runtime_pin_and_release_tags() -> None:
"""Runtime package pins remain independent of the SDK beta version."""
runtime_setup = _load_runtime_setup_module()
pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text())
assert runtime_setup.PACKAGE_NAME == "openai-codex-cli-bin"
assert runtime_setup.pinned_runtime_version() == pyproject["project"]["version"]
assert (
f"{runtime_setup.PACKAGE_NAME}=={pyproject['project']['version']}"
in pyproject["project"]["dependencies"]
)
assert runtime_setup._normalized_package_version("rust-v0.116.0-alpha.1") == "0.116.0a1"
assert runtime_setup._release_tag("0.116.0a1") == "rust-v0.116.0-alpha.1"
assert {
"package_name": runtime_setup.PACKAGE_NAME,
"sdk_version": pyproject["project"]["version"],
"runtime_pin": runtime_setup.pinned_runtime_version(),
"normalized_release_version": runtime_setup._normalized_package_version(
"rust-v0.116.0-alpha.1"
),
"release_tag": runtime_setup._release_tag("0.116.0a1"),
} == {
"package_name": "openai-codex-cli-bin",
"sdk_version": "0.1.0b1",
"runtime_pin": "0.132.0",
"normalized_release_version": "0.116.0a1",
"release_tag": "rust-v0.116.0-alpha.1",
}
@pytest.mark.parametrize(
@@ -491,23 +498,32 @@ def test_runtime_package_layout_is_included_by_wheel_config(
]
def test_stage_sdk_release_injects_exact_runtime_pin(tmp_path: Path) -> None:
def test_stage_sdk_release_preserves_reviewed_runtime_pin(tmp_path: Path) -> None:
script = _load_update_script_module()
staged = script.stage_python_sdk_package(
tmp_path / "sdk-stage",
"rust-v0.116.0-alpha.1",
"0.1.0b1",
)
pyproject = (staged / "pyproject.toml").read_text()
assert 'name = "openai-codex"' in pyproject
assert 'version = "0.116.0a1"' in pyproject
assert '"openai-codex-cli-bin==0.116.0a1"' in pyproject
pyproject = tomllib.loads((staged / "pyproject.toml").read_text())
assert {
"name": pyproject["project"]["name"],
"version": pyproject["project"]["version"],
"dependencies": pyproject["project"]["dependencies"],
} == {
"name": "openai-codex",
"version": "0.1.0b1",
"dependencies": [
"pydantic>=2.12",
"openai-codex-cli-bin==0.132.0",
],
}
assert (
'__version__ = "0.116.0a1"'
'__version__ = "0.1.0b1"'
not in (staged / "src" / "openai_codex" / "__init__.py").read_text()
)
assert (
'client_version: str = "0.116.0a1"'
'client_version: str = "0.1.0b1"'
not in (staged / "src" / "openai_codex" / "client.py").read_text()
)
assert not any((staged / "src" / "openai_codex").glob("bin/**"))
@@ -520,34 +536,41 @@ def test_stage_sdk_release_replaces_existing_staging_dir(tmp_path: Path) -> None
old_file.parent.mkdir(parents=True)
old_file.write_text("stale")
staged = script.stage_python_sdk_package(staging_dir, "0.116.0a1")
staged = script.stage_python_sdk_package(staging_dir, "0.1.0b1")
assert staged == staging_dir
assert not old_file.exists()
def test_staged_sdk_and_runtime_versions_match(tmp_path: Path) -> None:
def test_sdk_beta_release_can_pin_stable_runtime(tmp_path: Path) -> None:
script = _load_update_script_module()
package_archive = _write_fake_codex_package_archive(tmp_path, script)
sdk_stage = script.stage_python_sdk_package(
tmp_path / "sdk-stage",
"rust-v0.116.0-alpha.1",
"0.1.0b1",
)
runtime_stage = script.stage_python_runtime_package(
tmp_path / "runtime-stage",
"rust-v0.116.0-alpha.1",
"0.132.0",
package_archive,
)
sdk_pyproject = tomllib.loads((sdk_stage / "pyproject.toml").read_text())
runtime_pyproject = tomllib.loads((runtime_stage / "pyproject.toml").read_text())
assert sdk_pyproject["project"]["version"] == runtime_pyproject["project"]["version"]
assert sdk_pyproject["project"]["dependencies"] == [
"pydantic>=2.12",
"openai-codex-cli-bin==0.116.0a1",
]
assert {
"sdk_version": sdk_pyproject["project"]["version"],
"runtime_version": runtime_pyproject["project"]["version"],
"sdk_dependencies": sdk_pyproject["project"]["dependencies"],
} == {
"sdk_version": "0.1.0b1",
"runtime_version": "0.132.0",
"sdk_dependencies": [
"pydantic>=2.12",
"openai-codex-cli-bin==0.132.0",
],
}
def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None:
@@ -557,16 +580,16 @@ def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None:
[
"stage-sdk",
str(tmp_path / "sdk-stage"),
"--codex-version",
"rust-v0.116.0-alpha.1",
"--sdk-version",
"0.1.0b1",
]
)
def fake_generate_types() -> None:
calls.append("generate_types")
def fake_stage_sdk_package(_staging_dir: Path, codex_version: str) -> Path:
calls.append(f"stage_sdk:{codex_version}")
def fake_stage_sdk_package(_staging_dir: Path, sdk_version: str) -> Path:
calls.append(f"stage_sdk:{sdk_version}")
return tmp_path / "sdk-stage"
def fake_stage_runtime_package(
@@ -589,26 +612,7 @@ def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None:
script.run_command(args, ops)
assert calls == ["generate_types", "stage_sdk:0.116.0a1"]
def test_stage_sdk_rejects_mismatched_legacy_versions(tmp_path: Path) -> None:
script = _load_update_script_module()
args = script.parse_args(
[
"stage-sdk",
str(tmp_path / "sdk-stage"),
"--codex-version",
"0.116.0a1",
"--runtime-version",
"0.116.0a1",
"--sdk-version",
"0.115.0",
]
)
with pytest.raises(RuntimeError, match="versions must match"):
script.run_command(args, script.default_cli_ops())
assert calls == ["generate_types", "stage_sdk:0.1.0b1"]
def test_stage_runtime_stages_package_without_type_generation(tmp_path: Path) -> None:
+1 -1
View File
@@ -40,7 +40,7 @@ def test_generated_files_are_up_to_date():
# Regenerate contract artifacts via the pinned runtime package, not a local
# app-server binary from the checkout or CI environment.
assert importlib.metadata.version("openai-codex-cli-bin") == "0.131.0a4"
assert importlib.metadata.version("openai-codex-cli-bin") == "0.132.0"
env = os.environ.copy()
env.pop("CODEX_EXEC_PATH", None)
python_bin = str(Path(sys.executable).parent)