From 884c53793c4712513c57b7bf0b0b4c0df0fc6a03 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Mon, 3 Aug 2026 08:52:28 -0700 Subject: [PATCH 1/4] ci: migrate release-please to upstream action --- .github/workflows/create-releases.yml | 12 ++++++------ release-please-config.json | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index fc38031b96..fcb67cf5bc 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -9,20 +9,20 @@ jobs: name: release if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' runs-on: ubuntu-latest - environment: publish outputs: releases_created: ${{ steps.release.outputs.releases_created }} permissions: contents: write + issues: write + pull-requests: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: stainless-api/trigger-release-please@bb6677c5a04578eec1ccfd9e1913b5b78ed64c61 # v1.4.0 + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 id: release with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} + target-branch: main + config-file: release-please-config.json + manifest-file: .release-please-manifest.json build: name: build diff --git a/release-please-config.json b/release-please-config.json index 745ef5fd54..2e4b90d047 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,12 +1,10 @@ { + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "packages": { ".": {} }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", "include-v-in-tag": true, "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": false, "pull-request-header": "Automated Release PR", @@ -63,4 +61,4 @@ "extra-files": [ "src/openai/_version.py" ] -} \ No newline at end of file +} From 77f69f23f4f44819c0278ded7150ef4cc7e99e40 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Mon, 3 Aug 2026 09:07:11 -0700 Subject: [PATCH 2/4] ci: tighten release workflow permissions --- .github/workflows/create-releases.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index fcb67cf5bc..81d6e7c19e 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -11,6 +11,7 @@ jobs: runs-on: ubuntu-latest outputs: releases_created: ${{ steps.release.outputs.releases_created }} + # Release Please writes its PR branch, tags/releases, PR, and PR labels. permissions: contents: write issues: write @@ -20,6 +21,7 @@ jobs: - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 id: release with: + token: ${{ secrets.GITHUB_TOKEN }} target-branch: main config-file: release-please-config.json manifest-file: .release-please-manifest.json @@ -36,6 +38,8 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Set up Rye uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 @@ -64,7 +68,6 @@ jobs: # PyPI Trusted Publishing requires id-token: write. Keep it scoped to this # minimal upload-only job rather than the build job. permissions: - contents: read id-token: write steps: From 3cca60b70ba05d44b894d14a6ccb01949c16c822 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Mon, 3 Aug 2026 09:48:13 -0700 Subject: [PATCH 3/4] ci: run checks for release-please PRs --- .github/workflows/ci.yml | 19 +++++-- .github/workflows/create-releases.yml | 49 +++++++++++++++++++ .github/workflows/detect-breaking-changes.yml | 30 ++++++++++-- 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 505591c981..84704499ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,17 +14,26 @@ on: - 'stl-preview-head/**' - 'stl-preview-base/**' workflow_dispatch: + inputs: + release_pr: + description: Run required CI for a Release Please branch + required: false + default: false + type: boolean # Exercise the complete supported matrix and the next CPython prerelease # even when the repository has not changed. schedule: - cron: '47 9 * * *' +permissions: + contents: read + jobs: lint: timeout-minutes: 10 name: lint runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + if: (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -41,7 +50,7 @@ jobs: run: ./scripts/lint build: - if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + if: (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') timeout-minutes: 10 name: build permissions: @@ -111,7 +120,7 @@ jobs: timeout-minutes: 15 name: test (Python ${{ matrix.python-version }}) runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork strategy: fail-fast: false matrix: @@ -147,7 +156,7 @@ jobs: timeout-minutes: 20 name: test (HTTPX2) runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -224,7 +233,7 @@ jobs: timeout-minutes: 20 name: compatibility (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && !inputs.release_pr) continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index 81d6e7c19e..aa55f57e7e 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -11,6 +11,7 @@ jobs: runs-on: ubuntu-latest outputs: releases_created: ${{ steps.release.outputs.releases_created }} + release_pr_branch: ${{ steps.release_pr.outputs.branch }} # Release Please writes its PR branch, tags/releases, PR, and PR labels. permissions: contents: write @@ -26,6 +27,54 @@ jobs: config-file: release-please-config.json manifest-file: .release-please-manifest.json + - name: Capture release PR branch + if: steps.release.outputs.prs_created == 'true' + id: release_pr + env: + RELEASE_PR: ${{ steps.release.outputs.pr }} + run: | + branch="$(jq -er '.headBranchName | select(startswith("release-please--branches--"))' <<<"$RELEASE_PR")" + echo "branch=$branch" >> "$GITHUB_OUTPUT" + + release-pr-ci: + name: release PR CI + needs: release + if: ${{ needs.release.outputs.release_pr_branch != '' }} + runs-on: ubuntu-latest + # Dispatching workflows is the only operation in this job. Keeping it + # separate prevents Release Please from receiving Actions write access. + permissions: + actions: write + + steps: + - name: Run required checks for release PR + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + RELEASE_PR_BRANCH: ${{ needs.release.outputs.release_pr_branch }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const ref = process.env.RELEASE_PR_BRANCH; + await Promise.all([ + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'ci.yml', + ref, + inputs: { release_pr: 'true' }, + }), + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'detect-breaking-changes.yml', + ref, + inputs: { + release_pr: 'true', + base_sha: context.sha, + }, + }), + ]); + build: name: build needs: release diff --git a/.github/workflows/detect-breaking-changes.yml b/.github/workflows/detect-breaking-changes.yml index 5f4a5e1271..863e0e6e88 100644 --- a/.github/workflows/detect-breaking-changes.yml +++ b/.github/workflows/detect-breaking-changes.yml @@ -4,16 +4,36 @@ on: branches: - main - next + workflow_dispatch: + inputs: + release_pr: + description: Run checks for a Release Please branch + required: false + default: false + type: boolean + base_sha: + description: Base commit for breaking-change comparison + required: false + type: string + +permissions: + contents: read jobs: detect_breaking_changes: runs-on: 'ubuntu-latest' name: detect-breaking-changes - if: github.repository == 'openai/openai-python' + if: github.repository == 'openai/openai-python' && (github.event_name == 'pull_request' || (inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--'))) + env: + BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || inputs.base_sha }} + FETCH_DEPTH: 0 steps: - name: Calculate fetch-depth + if: github.event_name == 'pull_request' + env: + PR_COMMITS: ${{ github.event.pull_request.commits }} run: | - echo "FETCH_DEPTH=$(expr ${{ github.event.pull_request.commits }} + 1)" >> $GITHUB_ENV + echo "FETCH_DEPTH=$((PR_COMMITS + 1))" >> "$GITHUB_ENV" - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: @@ -30,17 +50,17 @@ jobs: rye sync --all-features - name: Detect removed symbols run: | - rye run python scripts/detect-breaking-changes.py "${{ github.event.pull_request.base.sha }}" + rye run python scripts/detect-breaking-changes.py "$BASE_SHA" - name: Detect breaking changes run: | test -f ./scripts/detect-breaking-changes || { echo "Missing scripts/detect-breaking-changes"; exit 1; } - ./scripts/detect-breaking-changes ${{ github.event.pull_request.base.sha }} + ./scripts/detect-breaking-changes "$BASE_SHA" agents_sdk: runs-on: 'ubuntu-latest' name: Detect Agents SDK regressions - if: github.repository == 'openai/openai-python' + if: github.repository == 'openai/openai-python' && (github.event_name == 'pull_request' || (inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--'))) steps: # Setup this sdk - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 From 770f79fcca70ea8442bfbc5aab9df2557449b6de Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Mon, 3 Aug 2026 10:01:23 -0700 Subject: [PATCH 4/4] ci: address release workflow review --- .github/workflows/ci.yml | 44 +++++++++++++++---- .github/workflows/create-releases.yml | 39 +++++++++++++--- .github/workflows/detect-breaking-changes.yml | 4 +- 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84704499ad..89011bf0f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + if: (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && github.ref == 'refs/heads/release-please--branches--main') || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -50,15 +50,16 @@ jobs: run: ./scripts/lint build: - if: (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + if: (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && github.ref == 'refs/heads/release-please--branches--main') || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') timeout-minutes: 10 name: build permissions: contents: read - id-token: write runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Set up Rye uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 @@ -97,19 +98,44 @@ jobs: - name: Validate HTTPX2 wheel on Python 3.14 run: python scripts/utils/validate-httpx2-wheel.py - - name: Get GitHub OIDC Token + - name: Stage tarball for Stainless upload if: |- github.repository == 'stainless-sdks/openai-python' && !startsWith(github.ref, 'refs/heads/stl/') + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: stainless-package-tarball + path: dist/ + if-no-files-found: error + retention-days: 1 + + stainless-upload: + name: upload tarball to Stainless + needs: build + if: github.repository == 'stainless-sdks/openai-python' && !startsWith(github.ref, 'refs/heads/stl/') + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Download tarball + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: stainless-package-tarball + path: dist/ + + - name: Get GitHub OIDC Token id: github-oidc uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball - if: |- - github.repository == 'stainless-sdks/openai-python' && - !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} @@ -120,7 +146,7 @@ jobs: timeout-minutes: 15 name: test (Python ${{ matrix.python-version }}) runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && github.ref == 'refs/heads/release-please--branches--main') || github.event.pull_request.head.repo.fork strategy: fail-fast: false matrix: @@ -156,7 +182,7 @@ jobs: timeout-minutes: 20 name: test (HTTPX2) runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--')) || github.event.pull_request.head.repo.fork + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.release_pr && github.ref == 'refs/heads/release-please--branches--main') || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index aa55f57e7e..51264e6dfa 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -12,6 +12,7 @@ jobs: outputs: releases_created: ${{ steps.release.outputs.releases_created }} release_pr_branch: ${{ steps.release_pr.outputs.branch }} + release_pr_number: ${{ steps.release_pr.outputs.number }} # Release Please writes its PR branch, tags/releases, PR, and PR labels. permissions: contents: write @@ -27,34 +28,54 @@ jobs: config-file: release-please-config.json manifest-file: .release-please-manifest.json - - name: Capture release PR branch + - name: Capture release PR if: steps.release.outputs.prs_created == 'true' id: release_pr env: RELEASE_PR: ${{ steps.release.outputs.pr }} run: | - branch="$(jq -er '.headBranchName | select(startswith("release-please--branches--"))' <<<"$RELEASE_PR")" + branch="$(jq -er '.headBranchName | select(. == "release-please--branches--main")' <<<"$RELEASE_PR")" + number="$(jq -er '.number | select(type == "number")' <<<"$RELEASE_PR")" echo "branch=$branch" >> "$GITHUB_OUTPUT" + echo "number=$number" >> "$GITHUB_OUTPUT" release-pr-ci: name: release PR CI needs: release if: ${{ needs.release.outputs.release_pr_branch != '' }} runs-on: ubuntu-latest - # Dispatching workflows is the only operation in this job. Keeping it - # separate prevents Release Please from receiving Actions write access. + # Read the release PR's current base SHA, then dispatch its required + # workflows. Keeping this separate prevents Release Please from receiving + # Actions write access. permissions: actions: write + pull-requests: read steps: - name: Run required checks for release PR uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: RELEASE_PR_BRANCH: ${{ needs.release.outputs.release_pr_branch }} + RELEASE_PR_NUMBER: ${{ needs.release.outputs.release_pr_number }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const ref = process.env.RELEASE_PR_BRANCH; + const expectedRef = process.env.RELEASE_PR_BRANCH; + const pullNumber = Number(process.env.RELEASE_PR_NUMBER); + if (!Number.isSafeInteger(pullNumber) || pullNumber <= 0) { + throw new Error('Release Please returned an invalid PR number'); + } + + const { data: pull } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullNumber, + }); + if (pull.head.ref !== expectedRef) { + throw new Error(`Release PR branch changed: expected ${expectedRef}, got ${pull.head.ref}`); + } + + const ref = pull.head.ref; await Promise.all([ github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, @@ -70,9 +91,15 @@ jobs: ref, inputs: { release_pr: 'true', - base_sha: context.sha, + base_sha: pull.base.sha, }, }), + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'codeql.yml', + ref, + }), ]); build: diff --git a/.github/workflows/detect-breaking-changes.yml b/.github/workflows/detect-breaking-changes.yml index 863e0e6e88..db660c03cf 100644 --- a/.github/workflows/detect-breaking-changes.yml +++ b/.github/workflows/detect-breaking-changes.yml @@ -23,7 +23,7 @@ jobs: detect_breaking_changes: runs-on: 'ubuntu-latest' name: detect-breaking-changes - if: github.repository == 'openai/openai-python' && (github.event_name == 'pull_request' || (inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--'))) + if: github.repository == 'openai/openai-python' && (github.event_name == 'pull_request' || (inputs.release_pr && github.ref == 'refs/heads/release-please--branches--main')) env: BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || inputs.base_sha }} FETCH_DEPTH: 0 @@ -60,7 +60,7 @@ jobs: agents_sdk: runs-on: 'ubuntu-latest' name: Detect Agents SDK regressions - if: github.repository == 'openai/openai-python' && (github.event_name == 'pull_request' || (inputs.release_pr && startsWith(github.ref, 'refs/heads/release-please--branches--'))) + if: github.repository == 'openai/openai-python' && (github.event_name == 'pull_request' || (inputs.release_pr && github.ref == 'refs/heads/release-please--branches--main')) steps: # Setup this sdk - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4